1use crate::ServerError;
2use crate::auth_pass::PassVerifier;
3use crate::cluster::ClusterHandle;
4use crate::config::file::load_config;
5use crate::config::types::ServiceProfile;
6use crate::health::{ReadinessState, SharedReadinessState, start_health_server};
7use crate::server::connection::WebSocketListener;
8use crate::server::connection::services::{LiminalConnectionServices, build_connection_services};
9use crate::server::connection::{
10 ConnectionSupervisor, LysInstall, configured_lys, start_context_watch,
11};
12use crate::server::listener::ServerListener;
13use crate::server::self_streams::owner::PublisherRuntime;
14use crate::server::shutdown::{ShutdownHandle, register_signal_handlers};
15use crate::services::production::ServiceBindings;
16
17use std::fmt;
18use std::path::Path;
19use std::sync::Arc;
20use std::time::Instant;
21
22#[path = "runtime_links.rs"]
23mod runtime_links;
24use runtime_links::ConfiguredLinks;
25#[path = "link_udp_endpoint.rs"]
26pub mod link_udp_endpoint;
27
28#[cfg(feature = "test-support")]
29pub use runtime_links::test_support as link_test_support;
30
31fn configured_authentication(
32 config: &crate::config::types::ServerConfig,
33) -> Result<(Option<Vec<u8>>, Option<PassVerifier>), ServerError> {
34 let auth_token = config
35 .auth
36 .as_ref()
37 .map(|auth| auth.token.clone().into_bytes());
38 let pass_verifier = config
39 .auth
40 .as_ref()
41 .map(PassVerifier::from_auth)
42 .transpose()?
43 .flatten();
44 Ok((auth_token, pass_verifier))
45}
46
47pub fn run_participant_log_migration(
60 config_path: &Path,
61) -> Result<crate::server::participant::ParticipantLogMigration, ServerError> {
62 let config = load_config(config_path)?;
63 let Some(persistence_path) = config.persistence_path.as_deref() else {
64 return Err(ServerError::ConfigValidation {
65 message: "the participant log migration needs persistence_path; an ephemeral store has no logs to migrate".to_owned(),
66 });
67 };
68 let Some(participant) = config.participant.as_ref() else {
69 return Err(ServerError::ConfigValidation {
70 message: "the participant log migration needs the [participant] section".to_owned(),
71 });
72 };
73 let store = crate::server::connection::services::open_persistent_store(persistence_path)?;
74 Ok(crate::server::participant::migrate_participant_logs(
75 &store,
76 participant,
77 )?)
78}
79
80pub fn run(config_path: &Path) -> Result<(), ServerError> {
86 let mut stages = StartStages::new();
87 let config = load_config(config_path)?;
88 stages.mark("config");
89
90 crate::metrics::init();
94
95 let readiness = SharedReadinessState::new(ReadinessState::default());
96 let health_server = start_health_server(config.health_listen_address, readiness.clone())?;
97 stages.mark("health");
98 let shutdown_handle = ShutdownHandle::new();
99 let signal_registration = register_signal_handlers(shutdown_handle.clone())?;
100
101 let (auth_token, pass_verifier) = configured_authentication(&config)?;
106 stages.mark("auth");
107 let (lys_ask, lys) = runtime_links::lys(&config)?;
113 stages.mark("lys");
114 let mut context_watch = None;
115
116 let mut self_stream_publisher = None;
124 let mut spine = None;
125 let (connection_supervisor, cluster_handle, mut links) = match config.services.profile()? {
126 ServiceProfile::Full => {
127 let full = start_full_profile(
128 &config,
129 (auth_token, pass_verifier),
130 (lys, lys_ask.as_ref()),
131 &shutdown_handle,
132 &health_server,
133 &readiness,
134 &mut stages,
135 )?;
136 context_watch = full.context_watch;
137 self_stream_publisher = full.self_stream_publisher;
138 spine = full.spine;
139 (full.connection_supervisor, full.cluster_handle, full.links)
140 }
141 ServiceProfile::WorkerFrontDoor => {
142 let services = build_connection_services(&config)?;
143 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
147 services,
148 auth_token,
149 pass_verifier,
150 lys,
151 config.limits,
152 shutdown_handle.clone(),
153 )?;
154 readiness.set_cluster_configured(false);
155 stages.mark("supervisor");
156 (connection_supervisor, None, ConfiguredLinks::default())
157 }
158 };
159
160 let services =
169 super::services_boot::install(&config, &connection_supervisor, lys_ask.as_ref())?;
170 let (mut listener, mut websocket_listener) = open_listeners(
171 &config,
172 connection_supervisor,
173 &readiness,
174 &mut stages,
175 spine.as_ref(),
176 )?;
177
178 services.listening(listener.local_addr())?;
180 links.start(&health_server.link_bridges(), &listener.supervisor())?;
181 stages.mark("services_registry");
182
183 tracing::debug!(
184 config_path = %config_path.display(),
185 listen_address = %config.listen_address,
186 health_listen_address = %health_server.local_addr(),
187 "liminal server configuration validated"
188 );
189
190 tracing::info!(
191 listen_address = %listener.local_addr(),
192 health_listen_address = %health_server.local_addr(),
193 start_stages = %stages,
194 time_to_listening_ms = u64::try_from(stages.began.elapsed().as_millis()).unwrap_or(u64::MAX),
197 "liminal server started"
198 );
199
200 shutdown_handle.wait();
201 readiness.set_listener_bound(false);
202 services.shutdown_begun();
205
206 if let Some(mut cluster_handle) = cluster_handle {
210 cluster_handle.shutdown();
211 }
212
213 let supervisor = listener.supervisor();
214 let shutdown_result = runtime_links::shutdown(
215 (&mut listener, websocket_listener.as_mut()),
216 &supervisor,
217 config.drain_timeout(),
218 (&mut links, &mut self_stream_publisher),
219 );
220 let participant_fatal = supervisor.participant_service_fatal();
221 let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
222 drop(context_watch);
224 drop(lys_ask);
225 drop(websocket_listener);
226 drop(signal_registration);
227 raise_in_precedence(
230 health_server.shutdown(),
231 raise_in_precedence(publisher_shutdown, shutdown_result),
232 )?;
233 participant_fatal?.map_or(Ok(()), |fatal| {
234 Err(ServerError::ParticipantServiceFatal { fatal })
235 })
236}
237
238struct FullProfile {
242 links: runtime_links::ConfiguredLinks,
243 connection_supervisor: ConnectionSupervisor,
244 spine: Option<Arc<haematite::EventStore>>,
246 cluster_handle: Option<ClusterHandle>,
247 context_watch: Option<crate::services::context_watch::ContextWatch>,
248 self_stream_publisher: Option<PublisherRuntime>,
249}
250
251fn start_full_profile(
256 config: &crate::config::types::ServerConfig,
257 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
258 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
259 shutdown_handle: &ShutdownHandle,
260 health_server: &crate::health::HealthServerHandle,
261 readiness: &SharedReadinessState,
262 stages: &mut StartStages,
263) -> Result<FullProfile, ServerError> {
264 let services = Arc::new(LiminalConnectionServices::from_config(config)?);
265 stages.mark("services");
266 let spine = services.durable_store().reclaim_spine();
269 if let Some(spine) = &spine {
270 health_server.install_spine(spine);
271 }
272 install_ephemeral_sweep_report(&services, health_server)?;
273 if let Some(record) = services.unloadable_conversation_record() {
284 health_server.install_unloadable_record(record);
285 }
286 if let Some(reissuer) = services.credential_reissuer() {
291 health_server.install_credential_reissuer(reissuer);
292 }
293 let channel_cluster = services.channel_cluster().clone();
294 let signals_owner = Arc::clone(&services);
295 let lys = lys
296 .map(|install| configured_service_bindings(install, config, &services, lys_ask))
297 .transpose()?;
298 let bindings = lys.as_ref().and_then(|install| install.services.clone());
299 let links = runtime_links::prepare(config, lys.as_ref(), &signals_owner)?;
300 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
301 services,
302 auth_token,
303 pass_verifier,
304 lys,
305 config.limits,
306 shutdown_handle.clone(),
307 )?;
308 stages.mark("supervisor");
309 let mut context_watch = None;
310 if let Some(bindings) = bindings.as_deref() {
311 context_watch = start_context_watch(signals_owner.as_ref(), bindings, &config.limits)?;
312 stages.mark("services");
313 }
314
315 let self_stream_publisher = start_self_stream_publisher(
316 &signals_owner,
317 &connection_supervisor,
318 config,
319 shutdown_handle,
320 )?;
321 stages.mark("incarnation");
322
323 let cluster_handle =
328 start_configured_cluster(&channel_cluster, config.cluster.as_ref(), readiness)?;
329 stages.mark("cluster");
330 Ok(FullProfile {
331 links,
332 connection_supervisor,
333 spine,
334 cluster_handle,
335 context_watch,
336 self_stream_publisher,
337 })
338}
339
340fn configured_service_bindings(
346 mut install: LysInstall,
347 config: &crate::config::types::ServerConfig,
348 services: &Arc<LiminalConnectionServices>,
349 lys_ask: Option<&tokio::runtime::Runtime>,
350) -> Result<LysInstall, ServerError> {
351 let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
352 return Ok(install);
353 };
354 let Some(runtime) = lys_ask else {
355 return Err(ServerError::ConfigValidation {
356 message: "auth.lys.services needs the Lys ask runtime; none was started".to_owned(),
357 });
358 };
359 let now = std::time::SystemTime::now()
360 .duration_since(std::time::UNIX_EPOCH)
361 .map_or(0, |elapsed| elapsed.as_secs());
362 install.services =
363 ServiceBindings::configure(lys, Arc::clone(services), runtime.handle().clone(), now)
364 .map_err(|error| ServerError::ConfigValidation {
365 message: format!("auth.lys.services: {error}"),
366 })?
367 .map(Arc::new);
368 Ok(install)
369}
370
371fn lys_for(
374 config: &crate::config::types::ServerConfig,
375 lys_ask: Option<&tokio::runtime::Runtime>,
376) -> Result<Option<LysInstall>, ServerError> {
377 config
378 .auth
379 .as_ref()
380 .map(|auth| configured_lys(auth, lys_ask.map(|runtime| runtime.handle().clone())))
381 .transpose()
382 .map(Option::flatten)
383}
384
385fn open_listeners(
390 config: &crate::config::types::ServerConfig,
391 supervisor: ConnectionSupervisor,
392 readiness: &SharedReadinessState,
393 stages: &mut StartStages,
394 spine: Option<&Arc<haematite::EventStore>>,
395) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
396 readiness.track_admission(supervisor.admission_readiness());
397 let listeners = bind_listeners(config, supervisor, readiness)?;
398 stages.mark("listeners");
399 if let Some(spine) = spine {
400 crate::server::connection::reclaim_placement::release_after_bind(spine, stages.began);
401 }
402 Ok(listeners)
403}
404
405fn bind_listeners(
406 config: &crate::config::types::ServerConfig,
407 supervisor: ConnectionSupervisor,
408 readiness: &SharedReadinessState,
409) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
410 let listener = ServerListener::bind(config, supervisor)?;
411 let websocket_listener = match config.websocket.as_ref() {
416 Some(websocket_config) => Some(WebSocketListener::bind(
417 websocket_config,
418 listener.supervisor(),
419 )?),
420 None => None,
421 };
422 readiness.set_config_loaded(true);
423 readiness.set_listener_bound(true);
424 Ok((listener, websocket_listener))
425}
426
427struct StartStages {
430 began: Instant,
431 marks: Vec<(&'static str, u128)>,
432}
433
434impl StartStages {
435 fn new() -> Self {
436 Self {
437 began: Instant::now(),
438 marks: Vec::with_capacity(8),
439 }
440 }
441
442 fn mark(&mut self, name: &'static str) {
444 self.marks.push((name, self.began.elapsed().as_millis()));
445 }
446}
447
448impl fmt::Display for StartStages {
451 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
452 let mut before = 0;
453 for (index, (name, at)) in self.marks.iter().enumerate() {
454 if index > 0 {
455 formatter.write_str(", ")?;
456 }
457 write!(formatter, "{name} {} ({at})", at.saturating_sub(before))?;
458 before = *at;
459 }
460 Ok(())
461 }
462}
463
464mod bootstrap;
465#[cfg(test)]
466pub(in crate::server) use bootstrap::bind_self_streams;
467pub(in crate::server) use bootstrap::start_self_stream_publisher;
468use bootstrap::{
469 finish_self_stream_publisher, install_ephemeral_sweep_report, raise_in_precedence,
470 start_configured_cluster,
471};
472#[cfg(test)]
473#[path = "runtime/tests.rs"]
474mod tests;