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"]
23pub(in crate::server) mod 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 health_server.install_listen_addresses(listener.local_addresses())?;
180 services.listening(listener.local_addr())?;
181 links.start(&health_server.link_bridges(), &listener.supervisor())?;
182 stages.mark("services_registry");
183
184 tracing::debug!(
185 config_path = %config_path.display(),
186 listen_address = %config.listen_address,
187 health_listen_address = %health_server.local_addr(),
188 "liminal server configuration validated"
189 );
190
191 listening::report_started(&listener, &health_server, &stages);
192
193 shutdown_handle.wait();
194 readiness.set_listener_bound(false);
195 services.shutdown_begun();
198
199 if let Some(mut cluster_handle) = cluster_handle {
203 cluster_handle.shutdown();
204 }
205
206 let supervisor = listener.supervisor();
207 let shutdown_result = runtime_links::shutdown(
208 &supervisor,
209 config.drain_timeout(),
210 || crate::server::shutdown::stop_accepting(&mut listener, websocket_listener.as_mut()),
211 (&mut links, || {
212 finish_self_stream_publisher(&mut self_stream_publisher)
213 }),
214 );
215 let participant_fatal = supervisor.participant_service_fatal();
216 let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
217 drop(context_watch);
219 drop(lys_ask);
220 drop(websocket_listener);
221 drop(signal_registration);
222 raise_in_precedence(
225 health_server.shutdown(),
226 raise_in_precedence(publisher_shutdown, shutdown_result),
227 )?;
228 participant_fatal?.map_or(Ok(()), |fatal| {
229 Err(ServerError::ParticipantServiceFatal { fatal })
230 })
231}
232
233struct FullProfile {
237 links: runtime_links::ConfiguredLinks,
238 connection_supervisor: ConnectionSupervisor,
239 spine: Option<Arc<haematite::EventStore>>,
241 cluster_handle: Option<ClusterHandle>,
242 context_watch: Option<crate::services::context_watch::ContextWatch>,
243 self_stream_publisher: Option<PublisherRuntime>,
244}
245
246fn start_full_profile(
251 config: &crate::config::types::ServerConfig,
252 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
253 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
254 shutdown_handle: &ShutdownHandle,
255 health_server: &crate::health::HealthServerHandle,
256 readiness: &SharedReadinessState,
257 stages: &mut StartStages,
258) -> Result<FullProfile, ServerError> {
259 let services = Arc::new(LiminalConnectionServices::from_config(config)?);
260 stages.mark("services");
261 let spine = services.durable_store().reclaim_spine();
264 if let Some(spine) = &spine {
265 health_server.install_spine(spine);
266 }
267 install_ephemeral_sweep_report(&services, health_server)?;
268 if let Some(record) = services.unloadable_conversation_record() {
279 health_server.install_unloadable_record(record);
280 }
281 if let Some(reissuer) = services.credential_reissuer() {
286 health_server.install_credential_reissuer(reissuer);
287 }
288 let channel_cluster = services.channel_cluster().clone();
289 let signals_owner = Arc::clone(&services);
290 let lys = lys
291 .map(|install| configured_service_bindings(install, config, &services, lys_ask))
292 .transpose()?;
293 let bindings = lys.as_ref().and_then(|install| install.services.clone());
294 let links = runtime_links::prepare(config, lys.as_ref(), &signals_owner)?;
295 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
296 services,
297 auth_token,
298 pass_verifier,
299 lys,
300 config.limits,
301 shutdown_handle.clone(),
302 )?;
303 stages.mark("supervisor");
304 let mut context_watch = None;
305 if let Some(bindings) = bindings.as_deref() {
306 context_watch = start_context_watch(signals_owner.as_ref(), bindings, &config.limits)?;
307 stages.mark("services");
308 }
309
310 let self_stream_publisher = start_self_stream_publisher(
311 &signals_owner,
312 &connection_supervisor,
313 config,
314 shutdown_handle,
315 )?;
316 stages.mark("incarnation");
317
318 let cluster_handle =
323 start_configured_cluster(&channel_cluster, config.cluster.as_ref(), readiness)?;
324 stages.mark("cluster");
325 Ok(FullProfile {
326 links,
327 connection_supervisor,
328 spine,
329 cluster_handle,
330 context_watch,
331 self_stream_publisher,
332 })
333}
334
335fn configured_service_bindings(
341 mut install: LysInstall,
342 config: &crate::config::types::ServerConfig,
343 services: &Arc<LiminalConnectionServices>,
344 lys_ask: Option<&tokio::runtime::Runtime>,
345) -> Result<LysInstall, ServerError> {
346 let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
347 return Ok(install);
348 };
349 let Some(runtime) = lys_ask else {
350 return Err(ServerError::ConfigValidation {
351 message: "auth.lys.services needs the Lys ask runtime; none was started".to_owned(),
352 });
353 };
354 let now = std::time::SystemTime::now()
355 .duration_since(std::time::UNIX_EPOCH)
356 .map_or(0, |elapsed| elapsed.as_secs());
357 install.services =
358 ServiceBindings::configure(lys, Arc::clone(services), runtime.handle().clone(), now)
359 .map_err(|error| ServerError::ConfigValidation {
360 message: format!("auth.lys.services: {error}"),
361 })?
362 .map(Arc::new);
363 Ok(install)
364}
365
366fn lys_for(
369 config: &crate::config::types::ServerConfig,
370 lys_ask: Option<&tokio::runtime::Runtime>,
371) -> Result<Option<LysInstall>, ServerError> {
372 config
373 .auth
374 .as_ref()
375 .map(|auth| configured_lys(auth, lys_ask.map(|runtime| runtime.handle().clone())))
376 .transpose()
377 .map(Option::flatten)
378}
379
380fn open_listeners(
385 config: &crate::config::types::ServerConfig,
386 supervisor: ConnectionSupervisor,
387 readiness: &SharedReadinessState,
388 stages: &mut StartStages,
389 spine: Option<&Arc<haematite::EventStore>>,
390) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
391 readiness.track_admission(supervisor.admission_readiness());
392 let listeners = bind_listeners(config, supervisor, readiness)?;
393 stages.mark("listeners");
394 if let Some(spine) = spine {
395 let release =
396 crate::server::connection::reclaim_placement::release_after_bind(spine, stages.began)?;
397 listeners.0.supervisor().retain_reclaim_release(release)?;
398 }
399 Ok(listeners)
400}
401
402fn bind_listeners(
403 config: &crate::config::types::ServerConfig,
404 supervisor: ConnectionSupervisor,
405 readiness: &SharedReadinessState,
406) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
407 let listener = ServerListener::bind(config, supervisor)?;
408 let websocket_listener = match config.websocket.as_ref() {
413 Some(websocket_config) => Some(WebSocketListener::bind(
414 websocket_config,
415 listener.supervisor(),
416 )?),
417 None => None,
418 };
419 readiness.set_config_loaded(true);
420 readiness.set_listener_bound(true);
421 Ok((listener, websocket_listener))
422}
423
424struct StartStages {
427 began: Instant,
428 marks: Vec<(&'static str, u128)>,
429}
430
431impl StartStages {
432 fn new() -> Self {
433 Self {
434 began: Instant::now(),
435 marks: Vec::with_capacity(8),
436 }
437 }
438
439 fn mark(&mut self, name: &'static str) {
441 self.marks.push((name, self.began.elapsed().as_millis()));
442 }
443}
444
445impl fmt::Display for StartStages {
448 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
449 let mut before = 0;
450 for (index, (name, at)) in self.marks.iter().enumerate() {
451 if index > 0 {
452 formatter.write_str(", ")?;
453 }
454 write!(formatter, "{name} {} ({at})", at.saturating_sub(before))?;
455 before = *at;
456 }
457 Ok(())
458 }
459}
460
461mod bootstrap;
462#[cfg(test)]
463pub(in crate::server) use bootstrap::bind_self_streams;
464pub(in crate::server) use bootstrap::start_self_stream_publisher;
465use bootstrap::{
466 finish_self_stream_publisher, install_ephemeral_sweep_report, raise_in_precedence,
467 start_configured_cluster,
468};
469#[cfg(test)]
470#[path = "runtime/tests.rs"]
471mod tests;
472
473mod listening;