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::boot_steps::timed;
8use crate::server::connection::services::{LiminalConnectionServices, build_connection_services};
9use crate::server::connection::{BoundWebSocket, WebSocketListener};
10use crate::server::connection::{ConnectionSupervisor, LysInstall, start_context_watch};
11use crate::server::listener::{BoundAddresses, ServerListener};
12use crate::server::self_streams::owner::PublisherRuntime;
13use crate::server::shutdown::{
14 ShutdownHandle, SignalShutdownRegistration, register_signal_handlers,
15};
16use crate::services::production::ServiceBindings;
17
18use std::path::Path;
19use std::sync::Arc;
20
21#[path = "runtime_links.rs"]
22pub(in crate::server) mod runtime_links;
23use runtime_links::ConfiguredLinks;
24#[path = "link_udp_endpoint.rs"]
25pub mod link_udp_endpoint;
26
27#[cfg(feature = "test-support")]
28pub use runtime_links::test_support as link_test_support;
29
30fn configured_authentication(
31 config: &crate::config::types::ServerConfig,
32) -> Result<(Option<Vec<u8>>, Option<PassVerifier>), ServerError> {
33 let auth_token = config
34 .auth
35 .as_ref()
36 .map(|auth| auth.token.clone().into_bytes());
37 let pass_verifier = config
38 .auth
39 .as_ref()
40 .map(PassVerifier::from_auth)
41 .transpose()?
42 .flatten();
43 Ok((auth_token, pass_verifier))
44}
45
46pub fn run_participant_log_migration(
59 config_path: &Path,
60) -> Result<crate::server::participant::ParticipantLogMigration, ServerError> {
61 let config = load_config(config_path)?;
62 let Some(persistence_path) = config.persistence_path.as_deref() else {
63 return Err(ServerError::ConfigValidation {
64 message: "the participant log migration needs persistence_path; an ephemeral store has no logs to migrate".to_owned(),
65 });
66 };
67 let Some(participant) = config.participant.as_ref() else {
68 return Err(ServerError::ConfigValidation {
69 message: "the participant log migration needs the [participant] section".to_owned(),
70 });
71 };
72 let store = crate::server::connection::services::open_persistent_store(persistence_path)?;
73 Ok(crate::server::participant::migrate_participant_logs(
74 &store,
75 participant,
76 )?)
77}
78
79pub fn run(config_path: &Path) -> Result<(), ServerError> {
85 let config = load_config(config_path)?;
86 run_config(config_path, &config, None)
87}
88
89pub trait Deployment {
91 #[must_use]
93 fn shutdown(&self) -> ShutdownHandle;
94 fn serve(
99 &mut self,
100 supervisor: ConnectionSupervisor,
101 services: Option<Arc<LiminalConnectionServices>>,
102 shutdown: ShutdownHandle,
103 ) -> Result<(), ServerError>;
104}
105
106pub fn run_with_deployment(
112 config_path: &Path,
113 config: &crate::config::ServerConfig,
114 deployment: &mut dyn Deployment,
115) -> Result<(), ServerError> {
116 run_config(config_path, config, Some(deployment))
117}
118
119fn run_config(
120 config_path: &Path,
121 config: &crate::config::ServerConfig,
122 deployment: Option<&mut dyn Deployment>,
123) -> Result<(), ServerError> {
124 let mut stages = StartStages::new();
125 stages.mark("config");
126
127 crate::metrics::init();
131
132 let readiness = SharedReadinessState::new(ReadinessState::default());
133 let health_server = start_health_server(
134 config.health_listen_address,
135 readiness.clone(),
136 crate::health::AccessHealth::from(config),
137 )?;
138 stages.mark("health");
139 let (shutdown_handle, signal_registration) = deployment_signals(deployment.as_deref())?;
140
141 let (auth_token, pass_verifier) = configured_authentication(config)?;
146 stages.mark("auth");
147 let bound = timed("listener_bind", || bind_wire(config))?;
152 let (lys_ask, lys, mut machine_issue) = runtime_links::lys(config)?;
158 stages.mark("lys");
159 let FullProfile {
160 connection_supervisor,
161 cluster_handle,
162 mut links,
163 services: typed_services,
164 context_watch,
165 mut self_stream_publisher,
166 spine,
167 } = start_profile(
168 config,
169 (auth_token, pass_verifier),
170 (lys, lys_ask.as_ref()),
171 &shutdown_handle,
172 &health_server,
173 &readiness,
174 &mut stages,
175 )?;
176
177 let (services, mut grant_stream) = bootstrap::lys_owners(
186 config,
187 &connection_supervisor,
188 lys_ask.as_ref(),
189 typed_services.as_ref(),
190 &health_server,
191 )?;
192 let (mut listener, mut websocket_listener) = timed("accept_start", || {
193 open_listeners(
194 bound,
195 connection_supervisor,
196 &readiness,
197 &mut stages,
198 spine.as_ref(),
199 )
200 })?;
201
202 health_server.install_listen_addresses(listener.local_addresses())?;
204 services.listening(listener.local_addr())?;
205 machine_issue.begin()?;
206 links.start(&health_server.link_bridges(), &listener.supervisor())?;
207 stages.mark("services_registry");
208
209 listening::report_started(&listener, &health_server, &stages, config, config_path);
210
211 let deployment_result = serve_deployment(
212 deployment,
213 listener.supervisor(),
214 typed_services.as_ref().map(Arc::clone),
215 &shutdown_handle,
216 );
217 shutdown_handle.initiate();
218 readiness.set_listener_bound(false);
219 services.shutdown_begun();
222
223 if let Some(mut cluster_handle) = cluster_handle {
227 cluster_handle.shutdown();
228 }
229
230 let supervisor = listener.supervisor();
231 health_server.release_drafts()?;
232 drop(typed_services);
233 let shutdown_result = runtime_links::shutdown(
234 &supervisor,
235 config.drain_timeout(),
236 || crate::server::shutdown::stop_accepting(&mut listener, websocket_listener.as_mut()),
237 (&mut links, || {
238 let grants = crate::auth_membership::grant_stream::finish_configured(&mut grant_stream);
239 let publisher = finish_self_stream_publisher(&mut self_stream_publisher);
240 grants.and(publisher)
241 }),
242 );
243 let participant_fatal = supervisor.participant_service_fatal();
244 let start_failure = supervisor.incarnation_start_failure();
245 let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
246 drop(context_watch);
248 let issue_finish = machine_issue.finish();
249 drop(lys_ask);
250 drop(websocket_listener);
251 drop(signal_registration);
252 bootstrap::run_answer(
255 start_failure,
256 raise_in_precedence(
257 health_server.shutdown(),
258 raise_in_precedence(
259 publisher_shutdown,
260 raise_in_precedence(issue_finish, shutdown_result),
261 ),
262 ),
263 participant_fatal,
264 )
265 .and(deployment_result)
266}
267
268fn deployment_signals(
271 deployment: Option<&dyn Deployment>,
272) -> Result<(ShutdownHandle, Option<SignalShutdownRegistration>), ServerError> {
273 deployment.map_or_else(
274 || {
275 let handle = ShutdownHandle::new();
276 let registration = register_signal_handlers(handle.clone())?;
277 Ok((handle, Some(registration)))
278 },
279 |deployment| Ok((deployment.shutdown(), None)),
280 )
281}
282
283fn serve_deployment(
286 deployment: Option<&mut dyn Deployment>,
287 supervisor: ConnectionSupervisor,
288 services: Option<Arc<LiminalConnectionServices>>,
289 shutdown: &ShutdownHandle,
290) -> Result<(), ServerError> {
291 let served = deployment.map_or_else(
292 || {
293 shutdown.wait();
294 Ok(())
295 },
296 |deployment| deployment.serve(supervisor, services, shutdown.clone()),
297 );
298 if let Err(error) = &served {
299 tracing::error!(%error, "deployment sibling failed; draining the exchange");
300 }
301 served
302}
303
304struct FullProfile {
308 services: Option<Arc<LiminalConnectionServices>>,
309 links: runtime_links::ConfiguredLinks,
310 connection_supervisor: ConnectionSupervisor,
311 spine: Option<Arc<haematite::EventStore>>,
313 cluster_handle: Option<ClusterHandle>,
314 context_watch: Option<crate::services::context_watch::ContextWatch>,
315 self_stream_publisher: Option<PublisherRuntime>,
316}
317
318fn start_profile(
319 config: &crate::config::types::ServerConfig,
320 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
321 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
322 shutdown: &ShutdownHandle,
323 health: &crate::health::HealthServerHandle,
324 readiness: &SharedReadinessState,
325 stages: &mut StartStages,
326) -> Result<FullProfile, ServerError> {
327 match config.services.profile()? {
328 ServiceProfile::Full => start_full_profile(
329 config,
330 (auth_token, pass_verifier),
331 (lys, lys_ask),
332 shutdown,
333 health,
334 readiness,
335 stages,
336 ),
337 ServiceProfile::WorkerFrontDoor => {
338 let services = build_connection_services(config)?;
339 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
340 services,
341 auth_token,
342 pass_verifier,
343 lys,
344 config.limits,
345 shutdown.clone(),
346 )?;
347 readiness.set_cluster_configured(false);
348 stages.mark("supervisor");
349 Ok(FullProfile {
350 services: None,
351 links: ConfiguredLinks::default(),
352 connection_supervisor,
353 spine: None,
354 cluster_handle: None,
355 context_watch: None,
356 self_stream_publisher: None,
357 })
358 }
359 }
360}
361
362fn start_full_profile(
367 config: &crate::config::types::ServerConfig,
368 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
369 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
370 shutdown_handle: &ShutdownHandle,
371 health_server: &crate::health::HealthServerHandle,
372 readiness: &SharedReadinessState,
373 stages: &mut StartStages,
374) -> Result<FullProfile, ServerError> {
375 let services = Arc::new(LiminalConnectionServices::from_config_with_runtime(
376 config,
377 lys_ask.map(|runtime| runtime.handle().clone()),
378 )?);
379 stages.mark("services");
380 let spine = services.durable_store().reclaim_spine();
383 if let Some(spine) = &spine {
384 health_server.install_spine(spine);
385 }
386 install_ephemeral_sweep_report(&services, health_server)?;
387 if let Some(record) = services.unloadable_conversation_record() {
398 health_server.install_unloadable_record(record);
399 }
400 if let Some(reissuer) = services.credential_reissuer() {
405 health_server.install_credential_reissuer(reissuer);
406 }
407 let channel_cluster = services.channel_cluster().clone();
408 let signals_owner = Arc::clone(&services);
409 let lys = lys
410 .map(|install| configured_service_bindings(install, config, &services, lys_ask))
411 .transpose()?;
412 let bindings = lys.as_ref().and_then(|install| install.services.clone());
413 let links = runtime_links::prepare(config, lys.as_ref(), &signals_owner)?;
414 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
415 services,
416 auth_token,
417 pass_verifier,
418 lys,
419 config.limits,
420 shutdown_handle.clone(),
421 )?;
422 stages.mark("supervisor");
423 let mut context_watch = None;
424 if let Some(bindings) = bindings.as_deref() {
425 context_watch = start_context_watch(signals_owner.as_ref(), bindings, &config.limits)?;
426 stages.mark("services");
427 }
428
429 let self_stream_publisher = start_self_stream_publisher(
430 &signals_owner,
431 &connection_supervisor,
432 config,
433 shutdown_handle,
434 )?;
435 stages.mark("incarnation");
436
437 let cluster_handle =
442 start_configured_cluster(&channel_cluster, config.cluster.as_ref(), readiness)?;
443 stages.mark("cluster");
444 Ok(FullProfile {
445 services: Some(signals_owner),
446 links,
447 connection_supervisor,
448 spine,
449 cluster_handle,
450 context_watch,
451 self_stream_publisher,
452 })
453}
454
455fn configured_service_bindings(
461 mut install: LysInstall,
462 config: &crate::config::types::ServerConfig,
463 services: &Arc<LiminalConnectionServices>,
464 lys_ask: Option<&tokio::runtime::Runtime>,
465) -> Result<LysInstall, ServerError> {
466 let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
467 return Ok(install);
468 };
469 let Some(runtime) = lys_ask else {
470 return Err(ServerError::ConfigValidation {
471 message: "auth.lys.services needs the Lys ask runtime; none was started".to_owned(),
472 });
473 };
474 let now = std::time::SystemTime::now()
475 .duration_since(std::time::UNIX_EPOCH)
476 .map_or(0, |elapsed| elapsed.as_secs());
477 install.services =
478 ServiceBindings::configure(lys, Arc::clone(services), runtime.handle().clone(), now)
479 .map_err(|error| ServerError::ConfigValidation {
480 message: format!("auth.lys.services: {error}"),
481 })?
482 .map(Arc::new);
483 Ok(install)
484}
485
486fn open_listeners(
491 bound: BoundWire,
492 supervisor: ConnectionSupervisor,
493 readiness: &SharedReadinessState,
494 stages: &mut StartStages,
495 spine: Option<&Arc<haematite::EventStore>>,
496) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
497 readiness.track_admission(supervisor.admission_readiness());
498 let listeners = bind_listeners(bound, supervisor, readiness)?;
499 stages.mark("listeners");
500 crate::server::boot_steps::finished("time_to_listening", stages.began);
501 if let Some(spine) = spine {
502 let release =
503 crate::server::connection::reclaim_placement::release_after_bind(spine, stages.began)?;
504 listeners.0.supervisor().retain_reclaim_release(release)?;
505 }
506 Ok(listeners)
507}
508
509#[derive(Debug)]
512struct BoundWire {
513 addresses: BoundAddresses,
514 websocket: Option<BoundWebSocket>,
515}
516
517fn bind_wire(config: &crate::config::types::ServerConfig) -> Result<BoundWire, ServerError> {
521 let addresses = BoundAddresses::bind(config)?;
522 let websocket = config
523 .websocket
524 .as_ref()
525 .map(BoundWebSocket::bind)
526 .transpose()?;
527 if let Some(websocket) = &websocket {
528 crate::server::listener::open_reach_warning(
529 websocket.local_addr(),
530 config.access_mode(),
531 config.open_reach(),
532 );
533 }
534 Ok(BoundWire {
535 addresses,
536 websocket,
537 })
538}
539
540fn bind_listeners(
541 bound: BoundWire,
542 supervisor: ConnectionSupervisor,
543 readiness: &SharedReadinessState,
544) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
545 let listener = ServerListener::accept_on(bound.addresses, supervisor)?;
546 let websocket_listener = bound
547 .websocket
548 .map(|websocket| WebSocketListener::accept_on(websocket, listener.supervisor()))
549 .transpose()?;
550 readiness.set_config_loaded(true);
551 readiness.set_listener_bound(true);
552 Ok((listener, websocket_listener))
553}
554
555#[cfg(test)]
556#[path = "runtime/bind_wire_tests.rs"]
557mod bind_wire_tests;
558
559#[path = "runtime/start_stages.rs"]
560mod start_stages;
561use start_stages::StartStages;
562
563mod bootstrap;
564#[cfg(any(test, feature = "test-support"))]
565pub(in crate::server) use bootstrap::bind_self_streams;
566pub(in crate::server) use bootstrap::start_self_stream_publisher;
567use bootstrap::{
568 finish_self_stream_publisher, install_ephemeral_sweep_report, raise_in_precedence,
569 start_configured_cluster,
570};
571#[cfg(test)]
572#[path = "runtime/tests.rs"]
573mod tests;
574
575mod listening;