hatchery-server 0.2.3

Host for hatchery subservices (harness, m4a, memory): one supervisor process, loopback control and health
Documentation
use hatchery_server::{build_subservices, control, ServerConfig, Supervisor};

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();
    if std::env::args().skip(1).any(|a| a == "--help" || a == "-h") {
        println!(
            "hatchery-server: configure with HATCHERY_SERVER_DATA_DIR (required), HATCHERY_SERVER_CONFIG (optional TOML), \
             HATCHERY_SERVER_CONTROL_LISTEN (loopback), HATCHERY_SERVER_CONTROL_TOKEN (bearer for start/stop)"
        );
        return;
    }
    let config = ServerConfig::from_env().unwrap_or_else(|error| fail(&error.to_string()));
    let supervisor = Supervisor::new(build_subservices(&config));
    for (name, error) in supervisor.start_autostart() {
        tracing::error!(subservice = name, %error, "subservice failed to start");
    }
    let listener = control::bind(config.control_listen)
        .await
        .unwrap_or_else(|error| fail(&format!("control listener: {error}")));
    tracing::info!("hatchery-server control listening (loopback)");
    #[cfg(feature = "m4a")]
    let hub = if config.m4a_enabled && config.provision_c2_addr.is_some() {
        let dir = config.data_dir.clone();
        match tokio::task::spawn_blocking(move || hatchery_server::hub::Hub::open(&dir)).await.expect("hub open task") {
            Ok(hub) => Some(std::sync::Arc::new(hub)),
            Err(error) => {
                tracing::error!(%error, "messenger and tasks not started");
                None
            }
        }
    } else {
        None
    };
    #[cfg(not(feature = "m4a"))]
    let hub = None;
    let serving = tokio::spawn(control::serve_with_hub(listener, supervisor.clone(), config.control_token.clone(), hub));
    shutdown_signal().await;
    tracing::info!("shutting down");
    supervisor.stop_all();
    serving.abort();
}

fn fail(message: &str) -> ! {
    eprintln!("hatchery-server: {message}");
    std::process::exit(2);
}

#[cfg(unix)]
async fn shutdown_signal() {
    use tokio::signal::unix::{signal, SignalKind};
    let mut term = signal(SignalKind::terminate()).expect("sigterm handler");
    tokio::select! {
        _ = tokio::signal::ctrl_c() => {}
        _ = term.recv() => {}
    }
}

#[cfg(not(unix))]
async fn shutdown_signal() {
    let _ = tokio::signal::ctrl_c().await;
}