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::{ConnectionSupervisor, LysInstall, start_context_watch};
10use crate::server::listener::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 (lys_ask, lys, mut machine_issue) = runtime_links::lys(&config)?;
109 stages.mark("lys");
110 let FullProfile {
111 connection_supervisor,
112 cluster_handle,
113 mut links,
114 services: typed_services,
115 context_watch,
116 mut self_stream_publisher,
117 spine,
118 } = start_profile(
119 &config,
120 (auth_token, pass_verifier),
121 (lys, lys_ask.as_ref()),
122 &shutdown_handle,
123 &health_server,
124 &readiness,
125 &mut stages,
126 )?;
127
128 let (services, mut grant_stream) = bootstrap::lys_owners(
137 &config,
138 &connection_supervisor,
139 lys_ask.as_ref(),
140 typed_services.as_ref(),
141 &health_server,
142 )?;
143 let (mut listener, mut websocket_listener) =
144 crate::server::boot_steps::timed("listener_bind", || {
145 open_listeners(
146 &config,
147 connection_supervisor,
148 &readiness,
149 &mut stages,
150 spine.as_ref(),
151 )
152 })?;
153
154 health_server.install_listen_addresses(listener.local_addresses())?;
156 services.listening(listener.local_addr())?;
157 machine_issue.begin()?;
158 links.start(&health_server.link_bridges(), &listener.supervisor())?;
159 stages.mark("services_registry");
160
161 tracing::debug!(
162 config_path = %config_path.display(),
163 listen_address = %config.listen_address,
164 health_listen_address = %health_server.local_addr(),
165 "liminal server configuration validated"
166 );
167
168 listening::report_started(&listener, &health_server, &stages);
169
170 shutdown_handle.wait();
171 readiness.set_listener_bound(false);
172 services.shutdown_begun();
175
176 if let Some(mut cluster_handle) = cluster_handle {
180 cluster_handle.shutdown();
181 }
182
183 let supervisor = listener.supervisor();
184 health_server.release_drafts()?;
185 drop(typed_services);
186 let shutdown_result = runtime_links::shutdown(
187 &supervisor,
188 config.drain_timeout(),
189 || crate::server::shutdown::stop_accepting(&mut listener, websocket_listener.as_mut()),
190 (&mut links, || {
191 let grants = crate::auth_membership::grant_stream::finish_configured(&mut grant_stream);
192 let publisher = finish_self_stream_publisher(&mut self_stream_publisher);
193 grants.and(publisher)
194 }),
195 );
196 let participant_fatal = supervisor.participant_service_fatal();
197 let start_failure = supervisor.incarnation_start_failure();
198 let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
199 drop(context_watch);
201 let issue_finish = machine_issue.finish();
202 drop(lys_ask);
203 drop(websocket_listener);
204 drop(signal_registration);
205 bootstrap::run_answer(
208 start_failure,
209 raise_in_precedence(
210 health_server.shutdown(),
211 raise_in_precedence(
212 publisher_shutdown,
213 raise_in_precedence(issue_finish, shutdown_result),
214 ),
215 ),
216 participant_fatal,
217 )
218}
219
220struct FullProfile {
224 services: Option<Arc<LiminalConnectionServices>>,
225 links: runtime_links::ConfiguredLinks,
226 connection_supervisor: ConnectionSupervisor,
227 spine: Option<Arc<haematite::EventStore>>,
229 cluster_handle: Option<ClusterHandle>,
230 context_watch: Option<crate::services::context_watch::ContextWatch>,
231 self_stream_publisher: Option<PublisherRuntime>,
232}
233
234fn start_profile(
235 config: &crate::config::types::ServerConfig,
236 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
237 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
238 shutdown: &ShutdownHandle,
239 health: &crate::health::HealthServerHandle,
240 readiness: &SharedReadinessState,
241 stages: &mut StartStages,
242) -> Result<FullProfile, ServerError> {
243 match config.services.profile()? {
244 ServiceProfile::Full => start_full_profile(
245 config,
246 (auth_token, pass_verifier),
247 (lys, lys_ask),
248 shutdown,
249 health,
250 readiness,
251 stages,
252 ),
253 ServiceProfile::WorkerFrontDoor => {
254 let services = build_connection_services(config)?;
255 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
256 services,
257 auth_token,
258 pass_verifier,
259 lys,
260 config.limits,
261 shutdown.clone(),
262 )?;
263 readiness.set_cluster_configured(false);
264 stages.mark("supervisor");
265 Ok(FullProfile {
266 services: None,
267 links: ConfiguredLinks::default(),
268 connection_supervisor,
269 spine: None,
270 cluster_handle: None,
271 context_watch: None,
272 self_stream_publisher: None,
273 })
274 }
275 }
276}
277
278fn start_full_profile(
283 config: &crate::config::types::ServerConfig,
284 (auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
285 (lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
286 shutdown_handle: &ShutdownHandle,
287 health_server: &crate::health::HealthServerHandle,
288 readiness: &SharedReadinessState,
289 stages: &mut StartStages,
290) -> Result<FullProfile, ServerError> {
291 let services = Arc::new(LiminalConnectionServices::from_config_with_runtime(
292 config,
293 lys_ask.map(|runtime| runtime.handle().clone()),
294 )?);
295 stages.mark("services");
296 let spine = services.durable_store().reclaim_spine();
299 if let Some(spine) = &spine {
300 health_server.install_spine(spine);
301 }
302 install_ephemeral_sweep_report(&services, health_server)?;
303 if let Some(record) = services.unloadable_conversation_record() {
314 health_server.install_unloadable_record(record);
315 }
316 if let Some(reissuer) = services.credential_reissuer() {
321 health_server.install_credential_reissuer(reissuer);
322 }
323 let channel_cluster = services.channel_cluster().clone();
324 let signals_owner = Arc::clone(&services);
325 let lys = lys
326 .map(|install| configured_service_bindings(install, config, &services, lys_ask))
327 .transpose()?;
328 let bindings = lys.as_ref().and_then(|install| install.services.clone());
329 let links = runtime_links::prepare(config, lys.as_ref(), &signals_owner)?;
330 let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
331 services,
332 auth_token,
333 pass_verifier,
334 lys,
335 config.limits,
336 shutdown_handle.clone(),
337 )?;
338 stages.mark("supervisor");
339 let mut context_watch = None;
340 if let Some(bindings) = bindings.as_deref() {
341 context_watch = start_context_watch(signals_owner.as_ref(), bindings, &config.limits)?;
342 stages.mark("services");
343 }
344
345 let self_stream_publisher = start_self_stream_publisher(
346 &signals_owner,
347 &connection_supervisor,
348 config,
349 shutdown_handle,
350 )?;
351 stages.mark("incarnation");
352
353 let cluster_handle =
358 start_configured_cluster(&channel_cluster, config.cluster.as_ref(), readiness)?;
359 stages.mark("cluster");
360 Ok(FullProfile {
361 services: Some(signals_owner),
362 links,
363 connection_supervisor,
364 spine,
365 cluster_handle,
366 context_watch,
367 self_stream_publisher,
368 })
369}
370
371fn configured_service_bindings(
377 mut install: LysInstall,
378 config: &crate::config::types::ServerConfig,
379 services: &Arc<LiminalConnectionServices>,
380 lys_ask: Option<&tokio::runtime::Runtime>,
381) -> Result<LysInstall, ServerError> {
382 let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
383 return Ok(install);
384 };
385 let Some(runtime) = lys_ask else {
386 return Err(ServerError::ConfigValidation {
387 message: "auth.lys.services needs the Lys ask runtime; none was started".to_owned(),
388 });
389 };
390 let now = std::time::SystemTime::now()
391 .duration_since(std::time::UNIX_EPOCH)
392 .map_or(0, |elapsed| elapsed.as_secs());
393 install.services =
394 ServiceBindings::configure(lys, Arc::clone(services), runtime.handle().clone(), now)
395 .map_err(|error| ServerError::ConfigValidation {
396 message: format!("auth.lys.services: {error}"),
397 })?
398 .map(Arc::new);
399 Ok(install)
400}
401
402fn open_listeners(
407 config: &crate::config::types::ServerConfig,
408 supervisor: ConnectionSupervisor,
409 readiness: &SharedReadinessState,
410 stages: &mut StartStages,
411 spine: Option<&Arc<haematite::EventStore>>,
412) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
413 readiness.track_admission(supervisor.admission_readiness());
414 let listeners = bind_listeners(config, supervisor, readiness)?;
415 stages.mark("listeners");
416 crate::server::boot_steps::finished("time_to_listening", stages.began);
417 if let Some(spine) = spine {
418 let release =
419 crate::server::connection::reclaim_placement::release_after_bind(spine, stages.began)?;
420 listeners.0.supervisor().retain_reclaim_release(release)?;
421 }
422 Ok(listeners)
423}
424
425fn bind_listeners(
426 config: &crate::config::types::ServerConfig,
427 supervisor: ConnectionSupervisor,
428 readiness: &SharedReadinessState,
429) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
430 let listener = ServerListener::bind(config, supervisor)?;
431 let websocket_listener = match config.websocket.as_ref() {
436 Some(websocket_config) => Some(WebSocketListener::bind(
437 websocket_config,
438 listener.supervisor(),
439 )?),
440 None => None,
441 };
442 readiness.set_config_loaded(true);
443 readiness.set_listener_bound(true);
444 Ok((listener, websocket_listener))
445}
446
447#[path = "runtime/start_stages.rs"]
448mod start_stages;
449use start_stages::StartStages;
450
451mod bootstrap;
452#[cfg(any(test, feature = "test-support"))]
453pub(in crate::server) use bootstrap::bind_self_streams;
454pub(in crate::server) use bootstrap::start_self_stream_publisher;
455use bootstrap::{
456 finish_self_stream_publisher, install_ephemeral_sweep_report, raise_in_precedence,
457 start_configured_cluster,
458};
459#[cfg(test)]
460#[path = "runtime/tests.rs"]
461mod tests;
462
463mod listening;