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(
93 config.health_listen_address,
94 readiness.clone(),
95 crate::health::AccessHealth::from(&config),
96 )?;
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 bound = crate::server::boot_steps::timed("listener_bind", || bind_wire(&config))?;
112 let (lys_ask, lys, mut machine_issue) = runtime_links::lys(&config)?;
118 stages.mark("lys");
119 let FullProfile {
120 connection_supervisor,
121 cluster_handle,
122 mut links,
123 services: typed_services,
124 context_watch,
125 mut self_stream_publisher,
126 spine,
127 } = start_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
137 let (services, mut grant_stream) = bootstrap::lys_owners(
146 &config,
147 &connection_supervisor,
148 lys_ask.as_ref(),
149 typed_services.as_ref(),
150 &health_server,
151 )?;
152 let (mut listener, mut websocket_listener) =
153 crate::server::boot_steps::timed("accept_start", || {
154 open_listeners(
155 bound,
156 connection_supervisor,
157 &readiness,
158 &mut stages,
159 spine.as_ref(),
160 )
161 })?;
162
163 health_server.install_listen_addresses(listener.local_addresses())?;
165 services.listening(listener.local_addr())?;
166 machine_issue.begin()?;
167 links.start(&health_server.link_bridges(), &listener.supervisor())?;
168 stages.mark("services_registry");
169
170 listening::report_started(&listener, &health_server, &stages, &config, config_path);
171
172 shutdown_handle.wait();
173 readiness.set_listener_bound(false);
174 services.shutdown_begun();
177
178 if let Some(mut cluster_handle) = cluster_handle {
182 cluster_handle.shutdown();
183 }
184
185 let supervisor = listener.supervisor();
186 health_server.release_drafts()?;
187 drop(typed_services);
188 let shutdown_result = runtime_links::shutdown(
189 &supervisor,
190 config.drain_timeout(),
191 || crate::server::shutdown::stop_accepting(&mut listener, websocket_listener.as_mut()),
192 (&mut links, || {
193 let grants = crate::auth_membership::grant_stream::finish_configured(&mut grant_stream);
194 let publisher = finish_self_stream_publisher(&mut self_stream_publisher);
195 grants.and(publisher)
196 }),
197 );
198 let participant_fatal = supervisor.participant_service_fatal();
199 let start_failure = supervisor.incarnation_start_failure();
200 let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
201 drop(context_watch);
203 let issue_finish = machine_issue.finish();
204 drop(lys_ask);
205 drop(websocket_listener);
206 drop(signal_registration);
207 bootstrap::run_answer(
210 start_failure,
211 raise_in_precedence(
212 health_server.shutdown(),
213 raise_in_precedence(
214 publisher_shutdown,
215 raise_in_precedence(issue_finish, shutdown_result),
216 ),
217 ),
218 participant_fatal,
219 )
220}
221
222struct FullProfile {
226 services: Option<Arc<LiminalConnectionServices>>,
227 links: runtime_links::ConfiguredLinks,
228 connection_supervisor: ConnectionSupervisor,
229 spine: Option<Arc<haematite::EventStore>>,
231 cluster_handle: Option<ClusterHandle>,
232 context_watch: Option<crate::services::context_watch::ContextWatch>,
233 self_stream_publisher: Option<PublisherRuntime>,
234}
235
236fn start_profile(
237 config: &crate::config::types::ServerConfig,
238 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
239 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
240 shutdown: &ShutdownHandle,
241 health: &crate::health::HealthServerHandle,
242 readiness: &SharedReadinessState,
243 stages: &mut StartStages,
244) -> Result<FullProfile, ServerError> {
245 match config.services.profile()? {
246 ServiceProfile::Full => start_full_profile(
247 config,
248 (auth_token, pass_verifier),
249 (lys, lys_ask),
250 shutdown,
251 health,
252 readiness,
253 stages,
254 ),
255 ServiceProfile::WorkerFrontDoor => {
256 let services = build_connection_services(config)?;
257 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
258 services,
259 auth_token,
260 pass_verifier,
261 lys,
262 config.limits,
263 shutdown.clone(),
264 )?;
265 readiness.set_cluster_configured(false);
266 stages.mark("supervisor");
267 Ok(FullProfile {
268 services: None,
269 links: ConfiguredLinks::default(),
270 connection_supervisor,
271 spine: None,
272 cluster_handle: None,
273 context_watch: None,
274 self_stream_publisher: None,
275 })
276 }
277 }
278}
279
280fn start_full_profile(
285 config: &crate::config::types::ServerConfig,
286 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
287 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
288 shutdown_handle: &ShutdownHandle,
289 health_server: &crate::health::HealthServerHandle,
290 readiness: &SharedReadinessState,
291 stages: &mut StartStages,
292) -> Result<FullProfile, ServerError> {
293 let services = Arc::new(LiminalConnectionServices::from_config_with_runtime(
294 config,
295 lys_ask.map(|runtime| runtime.handle().clone()),
296 )?);
297 stages.mark("services");
298 let spine = services.durable_store().reclaim_spine();
301 if let Some(spine) = &spine {
302 health_server.install_spine(spine);
303 }
304 install_ephemeral_sweep_report(&services, health_server)?;
305 if let Some(record) = services.unloadable_conversation_record() {
316 health_server.install_unloadable_record(record);
317 }
318 if let Some(reissuer) = services.credential_reissuer() {
323 health_server.install_credential_reissuer(reissuer);
324 }
325 let channel_cluster = services.channel_cluster().clone();
326 let signals_owner = Arc::clone(&services);
327 let lys = lys
328 .map(|install| configured_service_bindings(install, config, &services, lys_ask))
329 .transpose()?;
330 let bindings = lys.as_ref().and_then(|install| install.services.clone());
331 let links = runtime_links::prepare(config, lys.as_ref(), &signals_owner)?;
332 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
333 services,
334 auth_token,
335 pass_verifier,
336 lys,
337 config.limits,
338 shutdown_handle.clone(),
339 )?;
340 stages.mark("supervisor");
341 let mut context_watch = None;
342 if let Some(bindings) = bindings.as_deref() {
343 context_watch = start_context_watch(signals_owner.as_ref(), bindings, &config.limits)?;
344 stages.mark("services");
345 }
346
347 let self_stream_publisher = start_self_stream_publisher(
348 &signals_owner,
349 &connection_supervisor,
350 config,
351 shutdown_handle,
352 )?;
353 stages.mark("incarnation");
354
355 let cluster_handle =
360 start_configured_cluster(&channel_cluster, config.cluster.as_ref(), readiness)?;
361 stages.mark("cluster");
362 Ok(FullProfile {
363 services: Some(signals_owner),
364 links,
365 connection_supervisor,
366 spine,
367 cluster_handle,
368 context_watch,
369 self_stream_publisher,
370 })
371}
372
373fn configured_service_bindings(
379 mut install: LysInstall,
380 config: &crate::config::types::ServerConfig,
381 services: &Arc<LiminalConnectionServices>,
382 lys_ask: Option<&tokio::runtime::Runtime>,
383) -> Result<LysInstall, ServerError> {
384 let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
385 return Ok(install);
386 };
387 let Some(runtime) = lys_ask else {
388 return Err(ServerError::ConfigValidation {
389 message: "auth.lys.services needs the Lys ask runtime; none was started".to_owned(),
390 });
391 };
392 let now = std::time::SystemTime::now()
393 .duration_since(std::time::UNIX_EPOCH)
394 .map_or(0, |elapsed| elapsed.as_secs());
395 install.services =
396 ServiceBindings::configure(lys, Arc::clone(services), runtime.handle().clone(), now)
397 .map_err(|error| ServerError::ConfigValidation {
398 message: format!("auth.lys.services: {error}"),
399 })?
400 .map(Arc::new);
401 Ok(install)
402}
403
404fn open_listeners(
409 bound: BoundWire,
410 supervisor: ConnectionSupervisor,
411 readiness: &SharedReadinessState,
412 stages: &mut StartStages,
413 spine: Option<&Arc<haematite::EventStore>>,
414) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
415 readiness.track_admission(supervisor.admission_readiness());
416 let listeners = bind_listeners(bound, supervisor, readiness)?;
417 stages.mark("listeners");
418 crate::server::boot_steps::finished("time_to_listening", stages.began);
419 if let Some(spine) = spine {
420 let release =
421 crate::server::connection::reclaim_placement::release_after_bind(spine, stages.began)?;
422 listeners.0.supervisor().retain_reclaim_release(release)?;
423 }
424 Ok(listeners)
425}
426
427#[derive(Debug)]
430struct BoundWire {
431 addresses: BoundAddresses,
432 websocket: Option<BoundWebSocket>,
433}
434
435fn bind_wire(config: &crate::config::types::ServerConfig) -> Result<BoundWire, ServerError> {
439 let addresses = BoundAddresses::bind(config)?;
440 let websocket = config
441 .websocket
442 .as_ref()
443 .map(BoundWebSocket::bind)
444 .transpose()?;
445 if let Some(websocket) = &websocket {
446 crate::server::listener::open_reach_warning(
447 websocket.local_addr(),
448 config.access_mode(),
449 config.open_reach(),
450 );
451 }
452 Ok(BoundWire {
453 addresses,
454 websocket,
455 })
456}
457
458fn bind_listeners(
459 bound: BoundWire,
460 supervisor: ConnectionSupervisor,
461 readiness: &SharedReadinessState,
462) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
463 let listener = ServerListener::accept_on(bound.addresses, supervisor)?;
464 let websocket_listener = bound
465 .websocket
466 .map(|websocket| WebSocketListener::accept_on(websocket, listener.supervisor()))
467 .transpose()?;
468 readiness.set_config_loaded(true);
469 readiness.set_listener_bound(true);
470 Ok((listener, websocket_listener))
471}
472
473#[cfg(test)]
474#[path = "runtime/bind_wire_tests.rs"]
475mod bind_wire_tests;
476
477#[path = "runtime/start_stages.rs"]
478mod start_stages;
479use start_stages::StartStages;
480
481mod bootstrap;
482#[cfg(any(test, feature = "test-support"))]
483pub(in crate::server) use bootstrap::bind_self_streams;
484pub(in crate::server) use bootstrap::start_self_stream_publisher;
485use bootstrap::{
486 finish_self_stream_publisher, install_ephemeral_sweep_report, raise_in_precedence,
487 start_configured_cluster,
488};
489#[cfg(test)]
490#[path = "runtime/tests.rs"]
491mod tests;
492
493mod listening;