ordinary-api 0.9.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 path = entry.path();

                if let Ok(mut dir) = path.read_dir()
                    && dir.next().is_none()
                {
                    tracing::warn!(domain = %entry.file_name().display(), "not setup");
                    continue;
                }

                let config_path = path.join("ordinary.json");
                let killed_path = path.join("killed");

                match OrdinaryConfig::get(path) {
                    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 !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
}