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::path::Path;
18use std::sync::Arc;
19
20#[path = "runtime_links.rs"]
21pub(in crate::server) mod runtime_links;
22use runtime_links::ConfiguredLinks;
23#[path = "link_udp_endpoint.rs"]
24pub mod link_udp_endpoint;
25
26#[cfg(feature = "test-support")]
27pub use runtime_links::test_support as link_test_support;
28
29fn configured_authentication(
30 config: &crate::config::types::ServerConfig,
31) -> Result<(Option<Vec<u8>>, Option<PassVerifier>), ServerError> {
32 let auth_token = config
33 .auth
34 .as_ref()
35 .map(|auth| auth.token.clone().into_bytes());
36 let pass_verifier = config
37 .auth
38 .as_ref()
39 .map(PassVerifier::from_auth)
40 .transpose()?
41 .flatten();
42 Ok((auth_token, pass_verifier))
43}
44
45pub fn run_participant_log_migration(
58 config_path: &Path,
59) -> Result<crate::server::participant::ParticipantLogMigration, ServerError> {
60 let config = load_config(config_path)?;
61 let Some(persistence_path) = config.persistence_path.as_deref() else {
62 return Err(ServerError::ConfigValidation {
63 message: "the participant log migration needs persistence_path; an ephemeral store has no logs to migrate".to_owned(),
64 });
65 };
66 let Some(participant) = config.participant.as_ref() else {
67 return Err(ServerError::ConfigValidation {
68 message: "the participant log migration needs the [participant] section".to_owned(),
69 });
70 };
71 let store = crate::server::connection::services::open_persistent_store(persistence_path)?;
72 Ok(crate::server::participant::migrate_participant_logs(
73 &store,
74 participant,
75 )?)
76}
77
78pub fn run(config_path: &Path) -> Result<(), ServerError> {
84 let mut stages = StartStages::new();
85 let config = load_config(config_path)?;
86 stages.mark("config");
87
88 crate::metrics::init();
92
93 let readiness = SharedReadinessState::new(ReadinessState::default());
94 let health_server = start_health_server(config.health_listen_address, readiness.clone())?;
95 stages.mark("health");
96 let shutdown_handle = ShutdownHandle::new();
97 let signal_registration = register_signal_handlers(shutdown_handle.clone())?;
98
99 let (auth_token, pass_verifier) = configured_authentication(&config)?;
104 stages.mark("auth");
105 let (lys_ask, lys) = runtime_links::lys(&config)?;
111 stages.mark("lys");
112 let FullProfile {
113 connection_supervisor,
114 cluster_handle,
115 mut links,
116 services: typed_services,
117 context_watch,
118 mut self_stream_publisher,
119 spine,
120 } = start_profile(
121 &config,
122 (auth_token, pass_verifier),
123 (lys, lys_ask.as_ref()),
124 &shutdown_handle,
125 &health_server,
126 &readiness,
127 &mut stages,
128 )?;
129
130 let services = super::services_boot::install(
139 &config,
140 &connection_supervisor,
141 lys_ask.as_ref(),
142 typed_services.as_ref(),
143 &health_server,
144 )?;
145 let mut grant_stream =
146 crate::auth_membership::grant_stream::GrantStreamOwner::start_for_server(
147 &config,
148 &connection_supervisor,
149 &health_server,
150 lys_ask.as_ref(),
151 typed_services
152 .as_ref()
153 .and_then(|services| services.draft_decision_hook()),
154 )?;
155 let (mut listener, mut websocket_listener) = open_listeners(
156 &config,
157 connection_supervisor,
158 &readiness,
159 &mut stages,
160 spine.as_ref(),
161 )?;
162
163 health_server.install_listen_addresses(listener.local_addresses())?;
165 services.listening(listener.local_addr())?;
166 links.start(&health_server.link_bridges(), &listener.supervisor())?;
167 stages.mark("services_registry");
168
169 tracing::debug!(
170 config_path = %config_path.display(),
171 listen_address = %config.listen_address,
172 health_listen_address = %health_server.local_addr(),
173 "liminal server configuration validated"
174 );
175
176 listening::report_started(&listener, &health_server, &stages);
177
178 shutdown_handle.wait();
179 readiness.set_listener_bound(false);
180 services.shutdown_begun();
183
184 if let Some(mut cluster_handle) = cluster_handle {
188 cluster_handle.shutdown();
189 }
190
191 let supervisor = listener.supervisor();
192 health_server.release_drafts()?;
193 drop(typed_services);
194 let shutdown_result = runtime_links::shutdown(
195 &supervisor,
196 config.drain_timeout(),
197 || crate::server::shutdown::stop_accepting(&mut listener, websocket_listener.as_mut()),
198 (&mut links, || {
199 let grants = crate::auth_membership::grant_stream::finish_configured(&mut grant_stream);
200 let publisher = finish_self_stream_publisher(&mut self_stream_publisher);
201 grants.and(publisher)
202 }),
203 );
204 let participant_fatal = supervisor.participant_service_fatal();
205 let start_failure = supervisor.incarnation_start_failure();
206 let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
207 drop(context_watch);
209 drop(lys_ask);
210 drop(websocket_listener);
211 drop(signal_registration);
212 bootstrap::run_answer(
215 start_failure,
216 raise_in_precedence(
217 health_server.shutdown(),
218 raise_in_precedence(publisher_shutdown, shutdown_result),
219 ),
220 participant_fatal,
221 )
222}
223
224struct FullProfile {
228 services: Option<Arc<LiminalConnectionServices>>,
229 links: runtime_links::ConfiguredLinks,
230 connection_supervisor: ConnectionSupervisor,
231 spine: Option<Arc<haematite::EventStore>>,
233 cluster_handle: Option<ClusterHandle>,
234 context_watch: Option<crate::services::context_watch::ContextWatch>,
235 self_stream_publisher: Option<PublisherRuntime>,
236}
237
238fn start_profile(
239 config: &crate::config::types::ServerConfig,
240 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
241 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
242 shutdown: &ShutdownHandle,
243 health: &crate::health::HealthServerHandle,
244 readiness: &SharedReadinessState,
245 stages: &mut StartStages,
246) -> Result<FullProfile, ServerError> {
247 match config.services.profile()? {
248 ServiceProfile::Full => start_full_profile(
249 config,
250 (auth_token, pass_verifier),
251 (lys, lys_ask),
252 shutdown,
253 health,
254 readiness,
255 stages,
256 ),
257 ServiceProfile::WorkerFrontDoor => {
258 let services = build_connection_services(config)?;
259 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
260 services,
261 auth_token,
262 pass_verifier,
263 lys,
264 config.limits,
265 shutdown.clone(),
266 )?;
267 readiness.set_cluster_configured(false);
268 stages.mark("supervisor");
269 Ok(FullProfile {
270 services: None,
271 links: ConfiguredLinks::default(),
272 connection_supervisor,
273 spine: None,
274 cluster_handle: None,
275 context_watch: None,
276 self_stream_publisher: None,
277 })
278 }
279 }
280}
281
282fn start_full_profile(
287 config: &crate::config::types::ServerConfig,
288 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
289 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
290 shutdown_handle: &ShutdownHandle,
291 health_server: &crate::health::HealthServerHandle,
292 readiness: &SharedReadinessState,
293 stages: &mut StartStages,
294) -> Result<FullProfile, ServerError> {
295 let services = Arc::new(LiminalConnectionServices::from_config_with_runtime(
296 config,
297 lys_ask.map(|runtime| runtime.handle().clone()),
298 )?);
299 stages.mark("services");
300 let spine = services.durable_store().reclaim_spine();
303 if let Some(spine) = &spine {
304 health_server.install_spine(spine);
305 }
306 install_ephemeral_sweep_report(&services, health_server)?;
307 if let Some(record) = services.unloadable_conversation_record() {
318 health_server.install_unloadable_record(record);
319 }
320 if let Some(reissuer) = services.credential_reissuer() {
325 health_server.install_credential_reissuer(reissuer);
326 }
327 let channel_cluster = services.channel_cluster().clone();
328 let signals_owner = Arc::clone(&services);
329 let lys = lys
330 .map(|install| configured_service_bindings(install, config, &services, lys_ask))
331 .transpose()?;
332 let bindings = lys.as_ref().and_then(|install| install.services.clone());
333 let links = runtime_links::prepare(config, lys.as_ref(), &signals_owner)?;
334 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
335 services,
336 auth_token,
337 pass_verifier,
338 lys,
339 config.limits,
340 shutdown_handle.clone(),
341 )?;
342 stages.mark("supervisor");
343 let mut context_watch = None;
344 if let Some(bindings) = bindings.as_deref() {
345 context_watch = start_context_watch(signals_owner.as_ref(), bindings, &config.limits)?;
346 stages.mark("services");
347 }
348
349 let self_stream_publisher = start_self_stream_publisher(
350 &signals_owner,
351 &connection_supervisor,
352 config,
353 shutdown_handle,
354 )?;
355 stages.mark("incarnation");
356
357 let cluster_handle =
362 start_configured_cluster(&channel_cluster, config.cluster.as_ref(), readiness)?;
363 stages.mark("cluster");
364 Ok(FullProfile {
365 services: Some(signals_owner),
366 links,
367 connection_supervisor,
368 spine,
369 cluster_handle,
370 context_watch,
371 self_stream_publisher,
372 })
373}
374
375fn configured_service_bindings(
381 mut install: LysInstall,
382 config: &crate::config::types::ServerConfig,
383 services: &Arc<LiminalConnectionServices>,
384 lys_ask: Option<&tokio::runtime::Runtime>,
385) -> Result<LysInstall, ServerError> {
386 let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
387 return Ok(install);
388 };
389 let Some(runtime) = lys_ask else {
390 return Err(ServerError::ConfigValidation {
391 message: "auth.lys.services needs the Lys ask runtime; none was started".to_owned(),
392 });
393 };
394 let now = std::time::SystemTime::now()
395 .duration_since(std::time::UNIX_EPOCH)
396 .map_or(0, |elapsed| elapsed.as_secs());
397 install.services =
398 ServiceBindings::configure(lys, Arc::clone(services), runtime.handle().clone(), now)
399 .map_err(|error| ServerError::ConfigValidation {
400 message: format!("auth.lys.services: {error}"),
401 })?
402 .map(Arc::new);
403 Ok(install)
404}
405
406fn lys_for(
409 config: &crate::config::types::ServerConfig,
410 lys_ask: Option<&tokio::runtime::Runtime>,
411) -> Result<Option<LysInstall>, ServerError> {
412 config
413 .auth
414 .as_ref()
415 .map(|auth| configured_lys(auth, lys_ask.map(|runtime| runtime.handle().clone())))
416 .transpose()
417 .map(Option::flatten)
418}
419
420fn open_listeners(
425 config: &crate::config::types::ServerConfig,
426 supervisor: ConnectionSupervisor,
427 readiness: &SharedReadinessState,
428 stages: &mut StartStages,
429 spine: Option<&Arc<haematite::EventStore>>,
430) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
431 readiness.track_admission(supervisor.admission_readiness());
432 let listeners = bind_listeners(config, supervisor, readiness)?;
433 stages.mark("listeners");
434 crate::server::boot_steps::finished("time_to_listening", stages.began);
435 if let Some(spine) = spine {
436 let release =
437 crate::server::connection::reclaim_placement::release_after_bind(spine, stages.began)?;
438 listeners.0.supervisor().retain_reclaim_release(release)?;
439 }
440 Ok(listeners)
441}
442
443fn bind_listeners(
444 config: &crate::config::types::ServerConfig,
445 supervisor: ConnectionSupervisor,
446 readiness: &SharedReadinessState,
447) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
448 let listener = ServerListener::bind(config, supervisor)?;
449 let websocket_listener = match config.websocket.as_ref() {
454 Some(websocket_config) => Some(WebSocketListener::bind(
455 websocket_config,
456 listener.supervisor(),
457 )?),
458 None => None,
459 };
460 readiness.set_config_loaded(true);
461 readiness.set_listener_bound(true);
462 Ok((listener, websocket_listener))
463}
464
465#[path = "runtime/start_stages.rs"]
466mod start_stages;
467use start_stages::StartStages;
468
469mod bootstrap;
470#[cfg(any(test, feature = "test-support"))]
471pub(in crate::server) use bootstrap::bind_self_streams;
472pub(in crate::server) use bootstrap::start_self_stream_publisher;
473use bootstrap::{
474 finish_self_stream_publisher, install_ephemeral_sweep_report, raise_in_precedence,
475 start_configured_cluster,
476};
477#[cfg(test)]
478#[path = "runtime/tests.rs"]
479mod tests;
480
481mod listening;