1use std::path::Path;
2use std::sync::Arc;
3
4use crate::ServerError;
5use crate::auth_pass::PassVerifier;
6use crate::cluster::{self, ClusterHandle};
7use crate::config::file::load_config;
8use crate::config::types::{ClusterConfig, ServiceProfile};
9use crate::health::{ReadinessState, SharedReadinessState, start_health_server};
10use crate::server::connection::ConnectionSupervisor;
11use crate::server::connection::WebSocketListener;
12use crate::server::connection::services::{
13 ChannelCluster, LiminalConnectionServices, build_connection_services,
14};
15use crate::server::listener::ServerListener;
16use crate::server::shutdown::{ShutdownHandle, register_signal_handlers, run_shutdown_sequence};
17
18fn configured_authentication(
19 config: &crate::config::types::ServerConfig,
20) -> Result<(Option<Vec<u8>>, Option<PassVerifier>), ServerError> {
21 let auth_token = config
22 .auth
23 .as_ref()
24 .map(|auth| auth.token.clone().into_bytes());
25 let pass_verifier = config
26 .auth
27 .as_ref()
28 .and_then(|auth| auth.pass.as_ref())
29 .map(PassVerifier::from_config)
30 .transpose()?;
31 Ok((auth_token, pass_verifier))
32}
33
34pub fn run_participant_log_migration(
47 config_path: &Path,
48) -> Result<crate::server::participant::ParticipantLogMigration, ServerError> {
49 let config = load_config(config_path)?;
50 let Some(persistence_path) = config.persistence_path.as_deref() else {
51 return Err(ServerError::ConfigValidation {
52 message: "the participant log migration needs persistence_path; an ephemeral store has no logs to migrate".to_owned(),
53 });
54 };
55 let Some(participant) = config.participant.as_ref() else {
56 return Err(ServerError::ConfigValidation {
57 message: "the participant log migration needs the [participant] section".to_owned(),
58 });
59 };
60 let store = crate::server::connection::services::open_persistent_store(persistence_path)?;
61 Ok(crate::server::participant::migrate_participant_logs(
62 &store,
63 participant,
64 )?)
65}
66
67pub fn run(config_path: &Path) -> Result<(), ServerError> {
73 if config_path.as_os_str().is_empty() {
74 return Err(ServerError::ConfigLoad {
75 message: "configuration path is empty".to_owned(),
76 });
77 }
78
79 let config = load_config(config_path)?;
80
81 crate::metrics::init();
85
86 let readiness = SharedReadinessState::new(ReadinessState::default());
87 let health_server = start_health_server(config.health_listen_address, readiness.clone())?;
88 let shutdown_handle = ShutdownHandle::new();
89 let signal_registration = register_signal_handlers(shutdown_handle.clone())?;
90
91 let (auth_token, pass_verifier) = configured_authentication(&config)?;
96
97 let (connection_supervisor, cluster_handle) = match config.services.profile()? {
105 ServiceProfile::Full => {
106 let services = Arc::new(LiminalConnectionServices::from_config(&config)?);
107 if let Some(record) = services.unloadable_conversation_record() {
117 health_server.install_unloadable_record(record);
118 }
119 if let Some(reissuer) = services.credential_reissuer() {
124 health_server.install_credential_reissuer(reissuer);
125 }
126 let channel_cluster = services.channel_cluster().clone();
127 let connection_supervisor = ConnectionSupervisor::with_fatal_shutdown(
128 services,
129 auth_token,
130 pass_verifier,
131 config.limits,
132 shutdown_handle.clone(),
133 )?;
134
135 readiness.set_cluster_configured(config.cluster.is_some());
140 let cluster_handle = match config.cluster.as_ref() {
141 Some(cluster_config) => {
142 Some(start_cluster(&channel_cluster, cluster_config, &readiness)?)
143 }
144 None => None,
145 };
146 (connection_supervisor, cluster_handle)
147 }
148 ServiceProfile::WorkerFrontDoor => {
149 let services = build_connection_services(&config)?;
150 let connection_supervisor = ConnectionSupervisor::with_fatal_shutdown(
151 services,
152 auth_token,
153 pass_verifier,
154 config.limits,
155 shutdown_handle.clone(),
156 )?;
157 readiness.set_cluster_configured(false);
158 (connection_supervisor, None)
159 }
160 };
161
162 readiness.track_admission(connection_supervisor.admission_readiness());
169
170 let mut listener = ServerListener::bind(&config, connection_supervisor)?;
171 let mut websocket_listener = match config.websocket.as_ref() {
176 Some(websocket_config) => Some(WebSocketListener::bind(
177 websocket_config,
178 listener.supervisor(),
179 )?),
180 None => None,
181 };
182 readiness.set_config_loaded(true);
183 readiness.set_listener_bound(true);
184
185 tracing::debug!(
186 config_path = %config_path.display(),
187 listen_address = %config.listen_address,
188 health_listen_address = %health_server.local_addr(),
189 "liminal server configuration validated"
190 );
191
192 tracing::info!(
193 listen_address = %listener.local_addr(),
194 health_listen_address = %health_server.local_addr(),
195 "liminal server started"
196 );
197
198 shutdown_handle.wait();
199 readiness.set_listener_bound(false);
200
201 if let Some(mut cluster_handle) = cluster_handle {
205 cluster_handle.shutdown();
206 }
207
208 let supervisor = listener.supervisor();
209 let shutdown_result = run_shutdown_sequence(
210 &mut listener,
211 websocket_listener.as_mut(),
212 &supervisor,
213 config.drain_timeout(),
214 );
215 let participant_fatal = supervisor.participant_service_fatal();
216 drop(websocket_listener);
217 drop(signal_registration);
218 health_server.shutdown()?;
219 shutdown_result?;
220 participant_fatal?.map_or(Ok(()), |fatal| {
221 Err(ServerError::ParticipantServiceFatal { fatal })
222 })
223}
224
225fn start_cluster(
237 channel_cluster: &ChannelCluster,
238 cluster_config: &ClusterConfig,
239 readiness: &SharedReadinessState,
240) -> Result<ClusterHandle, ServerError> {
241 let resolver = channel_cluster
242 .resolver()
243 .cloned()
244 .ok_or_else(|| ServerError::ClusterJoin {
245 message: "clustering configured but channel supervisor has no distribution resolver"
246 .to_owned(),
247 })?;
248 let scheduler = channel_cluster.supervisor().scheduler();
249 let supervisor = channel_cluster.supervisor().clone();
250 let readiness = readiness.clone();
251 cluster::start(
252 &scheduler,
253 resolver,
254 cluster_config,
255 move |sync| {
256 supervisor.install_observer(Arc::new(sync));
257 },
258 move || readiness.set_cluster_membership_established(true),
259 )
260}
261
262#[cfg(test)]
263mod tests {
264 use std::net::SocketAddr;
265
266 use super::{ChannelCluster, ClusterConfig, SharedReadinessState, start_cluster};
267 use crate::ServerError;
268 use crate::health::{ClusterReadiness, ReadinessCondition, ReadinessState, readiness_check};
269 use crate::server::connection::services::LiminalConnectionServices;
270
271 fn unclustered_channel_cluster() -> Result<ChannelCluster, ServerError> {
275 Ok(LiminalConnectionServices::empty()?
276 .channel_cluster()
277 .clone())
278 }
279
280 fn clustered_but_unmet_readiness() -> SharedReadinessState {
281 SharedReadinessState::new(ReadinessState::new(
282 true,
283 true,
284 ClusterReadiness::Configured {
285 membership_established: false,
286 },
287 ))
288 }
289
290 fn sample_cluster_config() -> Result<ClusterConfig, Box<dyn std::error::Error>> {
291 let listen_address: SocketAddr = "127.0.0.1:0".parse()?;
292 Ok(ClusterConfig {
293 node_name: "node-under-test@127.0.0.1".to_owned(),
294 listen_address,
295 seed_nodes: Vec::new(),
296 cookie: "runtime-test-cookie".to_owned(),
297 })
298 }
299
300 #[test]
301 fn failed_cluster_start_leaves_membership_unestablished()
302 -> Result<(), Box<dyn std::error::Error>> {
303 let readiness = clustered_but_unmet_readiness();
304 let channel_cluster = unclustered_channel_cluster()?;
305 let config = sample_cluster_config()?;
306
307 let result = start_cluster(&channel_cluster, &config, &readiness);
310 assert!(
311 result.is_err(),
312 "start_cluster must fail without a distribution resolver"
313 );
314
315 let status = readiness_check(&readiness.snapshot());
317 assert!(
318 !status.ready,
319 "readiness must remain not-ready after a failed start"
320 );
321 assert!(
322 status
323 .unmet_conditions
324 .contains(&ReadinessCondition::ClusterMembershipEstablished),
325 "cluster membership gate must stay unmet after a failed start"
326 );
327
328 Ok(())
329 }
330}