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