use super::config::{get_module_runtime_config, render_module_config_for_oop};
use super::host::{init_logging_unified, init_panic_tracing, normalize_path};
use super::{AppConfig, RuntimeKind};
use crate::backends::LocalProcessBackend;
use crate::runtime::{
DbOptions, OopModuleSpawnConfig, OopSpawnOptions, RunOptions, ShutdownOptions, run, shutdown,
};
use anyhow::Result;
use figment::Figment;
use figment::providers::Serialized;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
fn spawn_signal_handler(cancel: CancellationToken, context: &str) {
let context_owned = context.to_owned();
tokio::spawn(async move {
match shutdown::wait_for_shutdown().await {
Ok(()) => {
tracing::info!(target: "", "------------------");
tracing::info!("{}: shutdown signal received", context_owned);
}
Err(e) => {
tracing::warn!(
error = %e,
"{}: signal handler failed, falling back to ctrl_c()",
context_owned
);
_ = tokio::signal::ctrl_c().await;
}
}
cancel.cancel();
});
}
pub async fn run_server(config: AppConfig) -> Result<()> {
init_procedure(&config).map_err(|e| {
tracing::error!(error = %e, "Initialization failed");
e
})?;
tracing::info!("Initializing modules...");
let instance_id = uuid::Uuid::new_v4();
tracing::info!(instance_id = %instance_id, "Generated process instance ID");
let cancel = CancellationToken::new();
spawn_signal_handler(cancel.clone(), "server");
let db_options = resolve_db_options(&config)?;
let oop_backend = LocalProcessBackend::new(cancel.clone());
let oop_options = build_oop_spawn_options(&config, oop_backend)?;
let run_options = RunOptions {
modules_cfg: Arc::new(config),
db: db_options,
shutdown: ShutdownOptions::Token(cancel.clone()),
clients: vec![],
instance_id,
oop: oop_options,
shutdown_deadline: None,
};
let result = run(run_options).await;
#[cfg(feature = "otel")]
tracing_shutdown();
result
}
pub async fn run_migrate(config: AppConfig) -> Result<()> {
init_procedure(&config).map_err(|e| {
tracing::error!(error = %e, "Initialization failed");
e
})?;
tracing::info!("Starting migration mode...");
let instance_id = uuid::Uuid::new_v4();
tracing::info!(instance_id = %instance_id, "Generated migration instance ID");
let cancel = CancellationToken::new();
spawn_signal_handler(cancel.clone(), "migration");
let db_options = resolve_db_options(&config)?;
if matches!(db_options, DbOptions::None) {
anyhow::bail!("Cannot run migrations: no database configuration found");
}
let registry = crate::registry::ModuleRegistry::discover_and_build()?;
tracing::info!(
module_count = registry.modules().len(),
"Discovered modules for migration"
);
let host = crate::runtime::HostRuntime::new(
registry,
Arc::new(config),
db_options,
Arc::new(crate::client_hub::ClientHub::new()),
cancel,
instance_id,
None, );
let result = host.run_migration_phases().await;
#[cfg(feature = "otel")]
tracing_shutdown();
result?;
tracing::info!("All migrations completed successfully");
Ok(())
}
fn resolve_db_options(config: &AppConfig) -> Result<DbOptions> {
if config.database.is_none() {
tracing::warn!("No global database section found; running without databases");
return Ok(DbOptions::None);
}
tracing::info!("Using DbManager with Figment-based configuration");
let figment = Figment::new().merge(Serialized::defaults(config));
let db_manager = Arc::new(modkit_db::DbManager::from_figment(
figment,
config.server.home_dir.clone(),
)?);
Ok(DbOptions::Manager(db_manager))
}
fn build_oop_spawn_options(
config: &AppConfig,
backend: LocalProcessBackend,
) -> Result<Option<OopSpawnOptions>> {
let home_dir = PathBuf::from(&config.server.home_dir);
let mut modules = Vec::new();
for module_name in config.modules.keys() {
if let Some(spawn_config) = try_build_oop_module_config(config, module_name, &home_dir)? {
modules.push(spawn_config);
}
}
if modules.is_empty() {
Ok(None)
} else {
tracing::info!(count = modules.len(), "Prepared OoP modules for spawning");
Ok(Some(OopSpawnOptions {
modules,
backend: Box::new(backend),
}))
}
}
fn try_build_oop_module_config(
config: &AppConfig,
module_name: &str,
home_dir: &Path,
) -> Result<Option<OopModuleSpawnConfig>> {
let Some(runtime_cfg) = get_module_runtime_config(config, module_name)? else {
return Ok(None);
};
if !matches!(runtime_cfg.mod_type, RuntimeKind::Oop) {
return Ok(None);
}
let exec_cfg = runtime_cfg.execution.as_ref().ok_or_else(|| {
anyhow::anyhow!("module '{module_name}' is type=oop but execution config is missing")
})?;
let binary = normalize_path(&exec_cfg.executable_path)?;
let spawn_args = exec_cfg.args.clone();
let env = exec_cfg.environment.clone();
let rendered_config = render_module_config_for_oop(config, module_name, home_dir)?;
let rendered_json = rendered_config.to_json()?;
tracing::debug!(
module = %module_name,
"Prepared OoP module config: db={}",
rendered_config.database.is_some()
);
Ok(Some(OopModuleSpawnConfig {
module_name: module_name.to_owned(),
binary,
args: spawn_args,
env,
working_directory: exec_cfg.working_directory.clone(),
rendered_config_json: rendered_json,
}))
}
pub fn init_procedure(config: &AppConfig) -> Result<()> {
#[cfg(feature = "otel")]
let otel_layer = if config.opentelemetry.tracing.enabled {
Some(crate::telemetry::init::init_tracing(&config.opentelemetry)?)
} else {
None
};
#[cfg(not(feature = "otel"))]
let otel_layer = None;
init_logging_unified(&config.logging, &config.server.home_dir, otel_layer);
init_panic_tracing();
#[cfg(feature = "otel")]
if let Err(e) = crate::telemetry::init::init_metrics_provider(&config.opentelemetry) {
tracing::error!(error = %e, "OpenTelemetry metrics not initialized");
}
#[cfg(feature = "otel")]
if config.opentelemetry.tracing.enabled
&& let Err(e) = crate::telemetry::init::otel_connectivity_probe(&config.opentelemetry)
{
tracing::error!(error = %e, "OTLP connectivity probe failed");
}
tracing::info_span!("startup_check", app = config.server.name).in_scope(|| {
tracing::info!("startup span alive - traces should be visible in Jaeger");
});
tracing::info!(
version = env!("CARGO_PKG_VERSION"),
rust_version = env!("CARGO_PKG_RUST_VERSION"),
"{} Server starting",
config.server.name,
);
Ok(())
}
#[cfg(feature = "otel")]
pub fn tracing_shutdown() {
crate::telemetry::init::shutdown_metrics();
crate::telemetry::init::shutdown_tracing();
}