use crate::ServerError;
use crate::auth_pass::PassVerifier;
use crate::cluster::ClusterHandle;
use crate::config::file::load_config;
use crate::config::types::ServiceProfile;
use crate::health::{ReadinessState, SharedReadinessState, start_health_server};
use crate::server::connection::services::{LiminalConnectionServices, build_connection_services};
use crate::server::connection::{BoundWebSocket, WebSocketListener};
use crate::server::connection::{ConnectionSupervisor, LysInstall, start_context_watch};
use crate::server::listener::{BoundAddresses, ServerListener};
use crate::server::self_streams::owner::PublisherRuntime;
use crate::server::shutdown::{ShutdownHandle, register_signal_handlers};
use crate::services::production::ServiceBindings;
use std::path::Path;
use std::sync::Arc;
#[path = "runtime_links.rs"]
pub(in crate::server) mod runtime_links;
use runtime_links::ConfiguredLinks;
#[path = "link_udp_endpoint.rs"]
pub mod link_udp_endpoint;
#[cfg(feature = "test-support")]
pub use runtime_links::test_support as link_test_support;
fn configured_authentication(
config: &crate::config::types::ServerConfig,
) -> Result<(Option<Vec<u8>>, Option<PassVerifier>), ServerError> {
let auth_token = config
.auth
.as_ref()
.map(|auth| auth.token.clone().into_bytes());
let pass_verifier = config
.auth
.as_ref()
.map(PassVerifier::from_auth)
.transpose()?
.flatten();
Ok((auth_token, pass_verifier))
}
pub fn run_participant_log_migration(
config_path: &Path,
) -> Result<crate::server::participant::ParticipantLogMigration, ServerError> {
let config = load_config(config_path)?;
let Some(persistence_path) = config.persistence_path.as_deref() else {
return Err(ServerError::ConfigValidation {
message: "the participant log migration needs persistence_path; an ephemeral store has no logs to migrate".to_owned(),
});
};
let Some(participant) = config.participant.as_ref() else {
return Err(ServerError::ConfigValidation {
message: "the participant log migration needs the [participant] section".to_owned(),
});
};
let store = crate::server::connection::services::open_persistent_store(persistence_path)?;
Ok(crate::server::participant::migrate_participant_logs(
&store,
participant,
)?)
}
pub fn run(config_path: &Path) -> Result<(), ServerError> {
let mut stages = StartStages::new();
let config = load_config(config_path)?;
stages.mark("config");
crate::metrics::init();
let readiness = SharedReadinessState::new(ReadinessState::default());
let health_server = start_health_server(config.health_listen_address, readiness.clone())?;
stages.mark("health");
let shutdown_handle = ShutdownHandle::new();
let signal_registration = register_signal_handlers(shutdown_handle.clone())?;
let (auth_token, pass_verifier) = configured_authentication(&config)?;
stages.mark("auth");
let bound = crate::server::boot_steps::timed("listener_bind", || bind_wire(&config))?;
let (lys_ask, lys, mut machine_issue) = runtime_links::lys(&config)?;
stages.mark("lys");
let FullProfile {
connection_supervisor,
cluster_handle,
mut links,
services: typed_services,
context_watch,
mut self_stream_publisher,
spine,
} = start_profile(
&config,
(auth_token, pass_verifier),
(lys, lys_ask.as_ref()),
&shutdown_handle,
&health_server,
&readiness,
&mut stages,
)?;
let (services, mut grant_stream) = bootstrap::lys_owners(
&config,
&connection_supervisor,
lys_ask.as_ref(),
typed_services.as_ref(),
&health_server,
)?;
let (mut listener, mut websocket_listener) =
crate::server::boot_steps::timed("accept_start", || {
open_listeners(
bound,
connection_supervisor,
&readiness,
&mut stages,
spine.as_ref(),
)
})?;
health_server.install_listen_addresses(listener.local_addresses())?;
services.listening(listener.local_addr())?;
machine_issue.begin()?;
links.start(&health_server.link_bridges(), &listener.supervisor())?;
stages.mark("services_registry");
tracing::debug!(
config_path = %config_path.display(),
listen_address = %config.listen_address,
health_listen_address = %health_server.local_addr(),
"liminal server configuration validated"
);
listening::report_started(&listener, &health_server, &stages);
shutdown_handle.wait();
readiness.set_listener_bound(false);
services.shutdown_begun();
if let Some(mut cluster_handle) = cluster_handle {
cluster_handle.shutdown();
}
let supervisor = listener.supervisor();
health_server.release_drafts()?;
drop(typed_services);
let shutdown_result = runtime_links::shutdown(
&supervisor,
config.drain_timeout(),
|| crate::server::shutdown::stop_accepting(&mut listener, websocket_listener.as_mut()),
(&mut links, || {
let grants = crate::auth_membership::grant_stream::finish_configured(&mut grant_stream);
let publisher = finish_self_stream_publisher(&mut self_stream_publisher);
grants.and(publisher)
}),
);
let participant_fatal = supervisor.participant_service_fatal();
let start_failure = supervisor.incarnation_start_failure();
let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
drop(context_watch);
let issue_finish = machine_issue.finish();
drop(lys_ask);
drop(websocket_listener);
drop(signal_registration);
bootstrap::run_answer(
start_failure,
raise_in_precedence(
health_server.shutdown(),
raise_in_precedence(
publisher_shutdown,
raise_in_precedence(issue_finish, shutdown_result),
),
),
participant_fatal,
)
}
struct FullProfile {
services: Option<Arc<LiminalConnectionServices>>,
links: runtime_links::ConfiguredLinks,
connection_supervisor: ConnectionSupervisor,
spine: Option<Arc<haematite::EventStore>>,
cluster_handle: Option<ClusterHandle>,
context_watch: Option<crate::services::context_watch::ContextWatch>,
self_stream_publisher: Option<PublisherRuntime>,
}
fn start_profile(
config: &crate::config::types::ServerConfig,
(auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
(lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
shutdown: &ShutdownHandle,
health: &crate::health::HealthServerHandle,
readiness: &SharedReadinessState,
stages: &mut StartStages,
) -> Result<FullProfile, ServerError> {
match config.services.profile()? {
ServiceProfile::Full => start_full_profile(
config,
(auth_token, pass_verifier),
(lys, lys_ask),
shutdown,
health,
readiness,
stages,
),
ServiceProfile::WorkerFrontDoor => {
let services = build_connection_services(config)?;
let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
services,
auth_token,
pass_verifier,
lys,
config.limits,
shutdown.clone(),
)?;
readiness.set_cluster_configured(false);
stages.mark("supervisor");
Ok(FullProfile {
services: None,
links: ConfiguredLinks::default(),
connection_supervisor,
spine: None,
cluster_handle: None,
context_watch: None,
self_stream_publisher: None,
})
}
}
}
fn start_full_profile(
config: &crate::config::types::ServerConfig,
(auth_token, pass_verifier): (Option<Vec<u8>>, Option<PassVerifier>),
(lys, lys_ask): (Option<LysInstall>, Option<&tokio::runtime::Runtime>),
shutdown_handle: &ShutdownHandle,
health_server: &crate::health::HealthServerHandle,
readiness: &SharedReadinessState,
stages: &mut StartStages,
) -> Result<FullProfile, ServerError> {
let services = Arc::new(LiminalConnectionServices::from_config_with_runtime(
config,
lys_ask.map(|runtime| runtime.handle().clone()),
)?);
stages.mark("services");
let spine = services.durable_store().reclaim_spine();
if let Some(spine) = &spine {
health_server.install_spine(spine);
}
install_ephemeral_sweep_report(&services, health_server)?;
if let Some(record) = services.unloadable_conversation_record() {
health_server.install_unloadable_record(record);
}
if let Some(reissuer) = services.credential_reissuer() {
health_server.install_credential_reissuer(reissuer);
}
let channel_cluster = services.channel_cluster().clone();
let signals_owner = Arc::clone(&services);
let lys = lys
.map(|install| configured_service_bindings(install, config, &services, lys_ask))
.transpose()?;
let bindings = lys.as_ref().and_then(|install| install.services.clone());
let links = runtime_links::prepare(config, lys.as_ref(), &signals_owner)?;
let connection_supervisor = ConnectionSupervisor::with_lys_and_fatal_shutdown(
services,
auth_token,
pass_verifier,
lys,
config.limits,
shutdown_handle.clone(),
)?;
stages.mark("supervisor");
let mut context_watch = None;
if let Some(bindings) = bindings.as_deref() {
context_watch = start_context_watch(signals_owner.as_ref(), bindings, &config.limits)?;
stages.mark("services");
}
let self_stream_publisher = start_self_stream_publisher(
&signals_owner,
&connection_supervisor,
config,
shutdown_handle,
)?;
stages.mark("incarnation");
let cluster_handle =
start_configured_cluster(&channel_cluster, config.cluster.as_ref(), readiness)?;
stages.mark("cluster");
Ok(FullProfile {
services: Some(signals_owner),
links,
connection_supervisor,
spine,
cluster_handle,
context_watch,
self_stream_publisher,
})
}
fn configured_service_bindings(
mut install: LysInstall,
config: &crate::config::types::ServerConfig,
services: &Arc<LiminalConnectionServices>,
lys_ask: Option<&tokio::runtime::Runtime>,
) -> Result<LysInstall, ServerError> {
let Some(lys) = config.auth.as_ref().and_then(|auth| auth.lys.as_ref()) else {
return Ok(install);
};
let Some(runtime) = lys_ask else {
return Err(ServerError::ConfigValidation {
message: "auth.lys.services needs the Lys ask runtime; none was started".to_owned(),
});
};
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |elapsed| elapsed.as_secs());
install.services =
ServiceBindings::configure(lys, Arc::clone(services), runtime.handle().clone(), now)
.map_err(|error| ServerError::ConfigValidation {
message: format!("auth.lys.services: {error}"),
})?
.map(Arc::new);
Ok(install)
}
fn open_listeners(
bound: BoundWire,
supervisor: ConnectionSupervisor,
readiness: &SharedReadinessState,
stages: &mut StartStages,
spine: Option<&Arc<haematite::EventStore>>,
) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
readiness.track_admission(supervisor.admission_readiness());
let listeners = bind_listeners(bound, supervisor, readiness)?;
stages.mark("listeners");
crate::server::boot_steps::finished("time_to_listening", stages.began);
if let Some(spine) = spine {
let release =
crate::server::connection::reclaim_placement::release_after_bind(spine, stages.began)?;
listeners.0.supervisor().retain_reclaim_release(release)?;
}
Ok(listeners)
}
#[derive(Debug)]
struct BoundWire {
addresses: BoundAddresses,
websocket: Option<BoundWebSocket>,
}
fn bind_wire(config: &crate::config::types::ServerConfig) -> Result<BoundWire, ServerError> {
let addresses = BoundAddresses::bind(config)?;
let websocket = config
.websocket
.as_ref()
.map(BoundWebSocket::bind)
.transpose()?;
Ok(BoundWire {
addresses,
websocket,
})
}
fn bind_listeners(
bound: BoundWire,
supervisor: ConnectionSupervisor,
readiness: &SharedReadinessState,
) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
let listener = ServerListener::accept_on(bound.addresses, supervisor)?;
let websocket_listener = bound
.websocket
.map(|websocket| WebSocketListener::accept_on(websocket, listener.supervisor()))
.transpose()?;
readiness.set_config_loaded(true);
readiness.set_listener_bound(true);
Ok((listener, websocket_listener))
}
#[cfg(test)]
#[path = "runtime/bind_wire_tests.rs"]
mod bind_wire_tests;
#[path = "runtime/start_stages.rs"]
mod start_stages;
use start_stages::StartStages;
mod bootstrap;
#[cfg(any(test, feature = "test-support"))]
pub(in crate::server) use bootstrap::bind_self_streams;
pub(in crate::server) use bootstrap::start_self_stream_publisher;
use bootstrap::{
finish_self_stream_publisher, install_ephemeral_sweep_report, raise_in_precedence,
start_configured_cluster,
};
#[cfg(test)]
#[path = "runtime/tests.rs"]
mod tests;
mod listening;