ordinary-api 0.8.0

API server for Ordinary
use crate::server::{
    OrdinaryApiServerState, ProvisionMode, Server, WrappedOrdinaryAppServer,
    WrappedOrdinaryProxyServer, ops,
};
use anyhow::bail;
use ordinary_auth::OsRng;
use ordinary_config::OrdinaryConfig;
use std::fs;
use std::path::Path;
use std::sync::Arc;
use tracing::{Instrument, Span};
use x25519_dalek::{PublicKey, StaticSecret};

#[allow(clippy::too_many_lines)]
pub(crate) async fn start(
    apps_dir: &Path,
    server_span: &Span,
    state: &Arc<OrdinaryApiServerState>,
) -> anyhow::Result<()> {
    let state_clone = state.clone();

    let Ok(shared_rt) = tokio::runtime::Handle::try_current() else {
        bail!("failed to get shared runtime");
    };

    Box::pin(async {
        // todo: sleep this for 2 seconds or something to wait for the server to get started up

        let mut servers = state_clone.servers.write().await;

        for entry in fs::read_dir(apps_dir)?.flatten() {
            let config_path = entry.path().join("ordinary.json");
            let killed_path = entry.path().join("killed");

            match fs::read(&config_path) {
                Ok(mut config_bytes) => match simd_json::from_slice::<OrdinaryConfig>(&mut config_bytes[..]) {
                    Ok(config) => {
                        let domain = config.domain.clone();

                        if state.stored_logs
                            && let Some(monitor) = &*state.monitor
                            && let Err(err) = monitor
                            .add(
                                config.domain.as_str(),
                                &config.cnames.clone().unwrap_or_default(),
                            )
                        {
                            tracing::error!(%err, "failed to add domain to log manager");
                        }

                        let app_span = Some(state.server_span.clone().in_scope(|| {
                            tracing::info_span!("app", domain = %domain)
                        }));

                        match ops::app::dbs(&state_clone, &config, &entry.path(), app_span.clone()).await {
                            Ok((auth, storage)) => {
                                match ops::app::start(
                                    &state_clone,
                                    &config,
                                    auth,
                                    storage,
                                    state_clone
                                        .provision_mode
                                        .as_ref()
                                        .unwrap_or(&ProvisionMode::Localhost).clone(),
                                    entry.path().join("certs"),
                                    shared_rt.clone(),
                                    app_span.clone(),
                                ).await {
                                    Ok((ports_res, app, terminate_tx, terminate_rx, stream_tx, configs)) => {
                                        for (domain, router) in &*app.proxies {
                                            servers.insert(
                                                domain.clone(),
                                                Server::Proxy(Arc::new(WrappedOrdinaryProxyServer {
                                                    service: router.0.clone(),
                                                    terminate_rx: terminate_rx.clone(),
                                                    configs: configs.clone(),
                                                })),
                                            );
                                        }

                                        let cnames = app.config.cnames.clone();

                                        let static_secret = StaticSecret::random_from_rng(OsRng);
                                        let public_key = PublicKey::from(&static_secret);

                                        let app = Arc::new(WrappedOrdinaryAppServer {
                                            port: ports_res.app.unwrap_or_default(),
                                            app,
                                            terminate_tx: terminate_tx.clone(),
                                            stream_tx,
                                            dh_keypair: (static_secret, public_key),
                                        });

                                        if let Some(cnames) = cnames {
                                            for cname in cnames {
                                                servers.insert(cname, Server::App(app.clone()));
                                            }
                                        }

                                        servers.insert(
                                            domain,
                                            Server::App(app),
                                        );

                                        if killed_path.exists() && let Some(app_span) = app_span {
                                            async {
                                                if let Ok(since) = tokio::fs::read_to_string(killed_path).await {
                                                    tracing::info!(%since, "killed");
                                                }
                                            }.instrument(app_span).await;
                                        }
                                    }
                                    Err(err) => {
                                        if let Some(app_span) = app_span {
                                            app_span.in_scope(|| {
                                                tracing::error!(%err, "failed to start app");
                                            });
                                        }
                                    }
                                }
                            }
                            Err(err) => {
                                if let Some(app_span) = app_span {
                                    app_span.in_scope(|| {
                                        tracing::error!(%err, "failed to init dbs");
                                    });
                                }
                            }
                        }
                    }
                    Err(err) => {
                        if let Some(path_str) = config_path.to_str() {
                            tracing::error!(%err, path = path_str, "failed to parse config file");
                        } else {
                            tracing::error!(%err, "failed to parse config file");
                        }
                    }
                },
                Err(err) => {
                    if !entry.path().join("erased").exists() {
                        if let Some(path_str) = config_path.to_str() {
                            tracing::warn!(%err, path = path_str, "failed to read config file");
                        } else {
                            tracing::warn!(%err, "failed to read config file");
                        }
                    }
                }
            }
        }

        drop(servers);
        anyhow::Ok(())

    }.instrument(server_span.clone())).await
}