Skip to main content

ordinary_api/server/
mod.rs

1// Copyright (C) 2026 Ordinary Labs, LLC.
2//
3// SPDX-License-Identifier: AGPL-3.0-only
4
5use futures_util::FutureExt;
6mod ops;
7
8use axum::http::{HeaderValue, Uri};
9use axum::http::{Request, header};
10use axum::response::Redirect;
11use axum::{Extension, extract::ConnectInfo};
12use hashbrown::HashMap;
13use hyper::HeaderMap;
14use hyper::body::Incoming;
15use hyper_util::rt::{TokioExecutor, TokioIo};
16use ordinary_app::server::OrdinaryAppServer;
17#[cfg(feature = "server")]
18use ordinary_config::OrdinaryApiLimits;
19use ordinary_monitor::Manager;
20use ordinary_storage::Storage;
21use ordinary_storage::saferlmdb::{self, EnvBuilder, Environment};
22use parking_lot::RwLock;
23use rand_chacha::rand_core::Rng;
24use rand_chacha::rand_core::SeedableRng;
25use sha2::{Digest, Sha256};
26use std::fs::File;
27use std::io::Write;
28use std::process;
29use tokio::net::TcpListener;
30use tokio::net::TcpStream;
31use tokio_rustls::LazyConfigAcceptor;
32use tokio_rustls::StartHandshake;
33use tower::Layer;
34
35use axum::Router;
36use axum::http::HeaderName;
37use axum::response::Response;
38use axum::routing::{get, patch, post, put};
39
40use axum::http::StatusCode;
41use tower_http::classify::ServerErrorsFailureClass;
42use tower_http::request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer};
43use uuid::Uuid;
44
45use std::net::SocketAddr;
46use std::path::Path;
47use std::path::PathBuf;
48use std::sync::Arc;
49use std::time::Duration;
50
51use parking_lot::Mutex;
52
53use rustls_acme::is_tls_alpn_challenge;
54use rustls_acme::{AcmeConfig, caches::DirCache};
55use tokio::io::AsyncWriteExt;
56use tokio_rustls::{TlsAcceptor, rustls::ServerConfig};
57use tower::{Service, ServiceBuilder};
58use tower_http::compression::CompressionLayer;
59use tower_http::decompression::RequestDecompressionLayer;
60use tower_http::trace::TraceLayer;
61use tracing::Span;
62
63use ordinary_auth::{Auth, AuthClient};
64
65use std::fs;
66
67use crate::{api_account_claims, api_invite_claims};
68use axum::middleware::Next;
69use base64::{Engine as B64Engine, engine::general_purpose::URL_SAFE_NO_PAD as b64};
70use getrandom::SysRng;
71use hickory_client::client::Client;
72use hickory_client::proto::runtime::TokioRuntimeProvider;
73use hickory_client::proto::udp::UdpClientStream;
74use ordinary_config::{
75    AccessTokenConfig, AuthConfig, HmacTokenAlgorithm, InviteConfig, InviteMode, MfaConfig,
76    OrdinaryApiConfig, OrdinaryConfig, PasswordConfig, RedactedHashAlg, RefreshTokenConfig,
77    SignedTokenAlgorithm, TotpConfig,
78};
79use ordinary_utils::middleware::modify_etag_for_encoding;
80use ordinary_utils::{
81    GMT_FORMAT, HeadersDebug, LatencyDisplay, REQUEST_ID_HEADER, WrappedRedactedHashingAlg,
82    get_host, redirect_service, response_for_panic, rustls_server_config, shutdown_signal,
83};
84use std::error::Error;
85use std::pin::pin;
86use time::UtcDateTime;
87use tokio::sync::watch;
88use tokio::sync::watch::{Receiver, Sender};
89use tokio::time::timeout;
90use tokio_rustls::rustls::crypto::ring;
91use tokio_rustls::rustls::server::Acceptor;
92use tokio_rustls::server::TlsStream;
93use tower_http::catch_panic::CatchPanicLayer;
94use tower_http::set_header::SetResponseHeaderLayer;
95use tower_http::timeout::TimeoutLayer;
96use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme};
97use utoipa::{Modify, OpenApi};
98use utoipa_swagger_ui::{Config, SwaggerUi};
99
100pub const ROOT: &str = "root";
101pub const ADMIN: &str = "admin";
102pub const APPLICATION: &str = "application";
103pub const AUTHENTICATION: &str = "authentication";
104
105#[derive(OpenApi)]
106#[openapi(
107    info(
108        title = "Ordinary API Server",
109        description = "
110## ordinary-api
111
112[![docs.rs](https://img.shields.io/docsrs/ordinary-api/0.6.0-pre.3)](https://docs.rs/ordinary-api/0.6.0-pre.3)
113[![dependency status](https://img.shields.io/deps-rs/ordinary-api/0.6.0-pre.3)](https://deps.rs/crate/ordinary-api/0.6.0-pre.3)
114
115### Access Token
116
117Use the [Ordinary CLI](https://crates.io/crates/ordinary) to get an access token.
118
119```sh
120ordinary accounts access get --min 15
121```
122",
123        contact(),
124    ),
125    modifiers(&Security),
126    nest(
127        (path = "/v1", api = OrdinaryApiOpenApi),
128    ),
129    tags(
130        (name = APPLICATION, description = "Application API"),
131        (name = ADMIN, description = "Admin API"),
132        (name = ROOT, description = "Root API"),
133        (name = AUTHENTICATION, description = "Authentication API"),
134    )
135)]
136struct ApiDoc;
137
138struct Security;
139
140impl Modify for Security {
141    fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
142        if let Some(components) = openapi.components.as_mut() {
143            components.add_security_scheme(
144                "access",
145                SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
146            );
147        }
148    }
149}
150
151#[derive(OpenApi)]
152#[openapi(paths(
153    ops::accounts::invite,
154    ops::accounts::list,
155    ops::accounts::delete,
156    ops::auth::registration_start,
157    ops::auth::registration_finish,
158    ops::auth::login_start,
159    ops::auth::login_finish,
160    ops::logs::root,
161    ops::logs::app,
162    ops::app::deploy,
163    ops::app::patch,
164    ops::app::kill,
165    ops::templates::upload,
166    ops::content::update,
167    ops::assets::write,
168    ops::models::items_list,
169    ops::actions::install,
170))]
171pub struct OrdinaryApiOpenApi;
172
173/// "admin" 0 | "read" 1 | "write" 2 | "update" 3 | "upload" 4 | "install" 5 | "deploy" 6 | "bridge" 7 | "kill" 8 | "erase" 9
174pub(crate) fn check_ordinary_auth(
175    state: &OrdinaryApiServerState,
176    headers: &HeaderMap,
177    permission: u8,
178    domain: &str,
179) -> Result<String, StatusCode> {
180    if let Some(auth_header) = headers.get("authorization")
181        && let Ok(str_val) = auth_header.to_str()
182        && let Some(b64_token) = str_val.strip_prefix("Bearer ")
183        && let Ok(token) = b64.decode(b64_token)
184    {
185        match state.auth.verify_access_token(&token) {
186            Ok((account, claims)) => {
187                let span = tracing::info_span!("permission", account);
188
189                span.in_scope(|| {
190                    if account == "root" {
191                        return Ok(account.to_string());
192                    }
193
194                    if domain == "root" {
195                        tracing::error!("root-only operation disallowed");
196                        return Err(StatusCode::UNAUTHORIZED);
197                    }
198
199                    let check_domain = claims.idx(1).as_str();
200
201                    if check_domain != domain {
202                        tracing::error!(domain, "token domain '{check_domain}' does not match");
203                        return Err(StatusCode::UNAUTHORIZED);
204                    }
205
206                    let permissions = claims.idx(2).as_vector();
207
208                    for claim_permission in &permissions {
209                        if claim_permission.as_u8() == 0 || claim_permission.as_u8() == permission {
210                            tracing::info!(
211                                role = match claim_permission.as_u8() {
212                                    0 => "admin",
213                                    1 => "read",
214                                    2 => "write",
215                                    3 => "update",
216                                    4 => "upload",
217                                    5 => "install",
218                                    6 => "deploy",
219                                    7 => "bridge",
220                                    8 => "kill",
221                                    9 => "erase",
222                                    _ => "invalid",
223                                },
224                                "granted"
225                            );
226
227                            return Ok(account.to_string());
228                        }
229                    }
230
231                    tracing::error!(
232                        permission = match permission {
233                            0 => "admin",
234                            1 => "read",
235                            2 => "write",
236                            3 => "update",
237                            4 => "upload",
238                            5 => "install",
239                            6 => "deploy",
240                            7 => "bridge",
241                            8 => "kill",
242                            9 => "erase",
243                            _ => "invalid",
244                        },
245                        "not in token permissions"
246                    );
247                    Err(StatusCode::UNAUTHORIZED)
248                })
249            }
250            Err(err) => {
251                tracing::error!(%err, "access denied");
252                Err(StatusCode::UNAUTHORIZED)
253            }
254        }
255    } else {
256        tracing::error!("invalid token");
257        Err(StatusCode::UNAUTHORIZED)
258    }
259}
260
261pub struct WrappedOrdinaryAppServer {
262    port: u16,
263    app: Arc<OrdinaryAppServer>,
264    kill_tx: tokio::sync::broadcast::Sender<()>,
265    stream_tx: tokio::sync::mpsc::UnboundedSender<(StartHandshake<TcpStream>, SocketAddr, Span)>,
266}
267
268type Dbs = HashMap<String, (Arc<Environment>, Arc<Auth>, Arc<Storage>)>;
269
270#[allow(clippy::struct_excessive_bools)]
271pub struct OrdinaryApiServerState {
272    pub domain: String,
273    pub secure: bool,
274    pub secure_cookies: bool,
275    pub log_headers: bool,
276    pub log_ips: bool,
277    pub log_size: bool,
278    pub auth: Arc<Auth>,
279    pub apps: Arc<RwLock<HashMap<String, Arc<WrappedOrdinaryAppServer>>>>,
280    pub apps_dir: PathBuf,
281    pub dbs: Arc<Mutex<Dbs>>,
282    pub env_name: String,
283    pub provision_mode: Option<ProvisionMode>,
284    pub dedicated_ports: bool,
285    pub server_span: Span,
286    pub log_manager: Arc<Option<Manager>>,
287    pub log_index: bool,
288    pub limits: String,
289    pub config: OrdinaryApiConfig,
290
291    pub signal_tx: Arc<RwLock<Option<Sender<()>>>>,
292    pub close_rx: Arc<RwLock<Option<Receiver<()>>>>,
293
294    pub dns_client: Arc<tokio::sync::Mutex<Client>>,
295}
296
297#[derive(PartialEq, Clone)]
298pub enum ProvisionMode {
299    Localhost,
300    Staging,
301    Production,
302}
303
304pub enum SecurityMode<T: AsRef<Path>> {
305    Insecure,
306    Secure(T, ProvisionMode),
307}
308
309pub struct OrdinaryApiServer {
310    auth: Arc<Auth>,
311    _env: Arc<Environment>,
312    config: OrdinaryApiConfig,
313    apps: Arc<RwLock<HashMap<String, Arc<WrappedOrdinaryAppServer>>>>,
314    apps_dir: PathBuf,
315    log_manager: Arc<Option<Manager>>,
316}
317
318#[cfg(feature = "server")]
319impl OrdinaryApiServer {
320    #[allow(clippy::similar_names)]
321    pub fn init(
322        env_name: &str,
323        domain: &str,
324        password: &str,
325        base_dir_path: impl AsRef<Path>,
326        storage_size: usize,
327        log_manager: Arc<Option<Manager>>,
328    ) -> Result<(String, Vec<u8>), Box<dyn Error>> {
329        let span = tracing::info_span!("init", env = %env_name, pid = process::id());
330
331        span.in_scope(|| {
332            let config = OrdinaryApiConfig {
333                // todo: pass in as a flag
334                public_dns_ip: [8, 8, 8, 8],
335                env_name: env_name.to_string(),
336                limits: OrdinaryApiLimits::default(),
337            };
338            let config_file = serde_json::to_string_pretty(&config)?;
339
340            let environment_dir_path = base_dir_path.as_ref().join("environments").join(env_name);
341            let data_path = environment_dir_path.join("data");
342
343            if data_path.exists() {
344                return Err("environment already initialized".into());
345            }
346
347            fs::write(environment_dir_path.join("ordinaryd.json"), config_file)?;
348
349            let api_server =
350                OrdinaryApiServer::new(env_name, domain, base_dir_path, storage_size, log_manager)?;
351
352            let mut input = domain.as_bytes().to_vec();
353            input.extend_from_slice(b"root");
354
355            let mut password_input = input.clone();
356            password_input.extend_from_slice(password.as_bytes());
357
358            let mut hasher = Sha256::new();
359            hasher.update(&password_input);
360            let password = hasher.finalize().to_vec();
361
362            let invite_token = api_server.auth.api_invite_get(domain, "root", None)?;
363
364            let (state, reg_start_req) =
365                AuthClient::registration_start_req(b"root", &password, Some(invite_token))?;
366            let reg_start_res = api_server
367                .auth
368                .registration_start(reg_start_req, None, None)?;
369
370            let (private_key, reg_finish_req) =
371                AuthClient::registration_finish_req(b"root", &password, &state, &reg_start_res)?;
372            let (reg_finish_res, ..) = api_server.auth.registration_finish(reg_finish_req, None)?;
373
374            let (totp, _recovery_codes) = AuthClient::decrypt_totp_mfa(
375                &reg_finish_res,
376                private_key,
377                env_name.to_string(),
378                "root".into(),
379            )?;
380
381            Ok((totp.get_url(), totp.secret))
382        })
383    }
384
385    pub fn new(
386        env_name: &str,
387        domain: &str,
388        base_dir_path: impl AsRef<Path>,
389        storage_size: usize,
390        log_manager: Arc<Option<Manager>>,
391    ) -> Result<OrdinaryApiServer, Box<dyn Error>> {
392        use ordinary_config::{HmacTokenConfig, SignedTokenConfig};
393
394        let span = tracing::info_span!("setup", env = %env_name, pid = process::id());
395
396        span.in_scope(|| {
397            let environment_dir_path = base_dir_path.as_ref().join("environments").join(env_name);
398
399            let config: OrdinaryApiConfig = serde_json::from_str(&fs::read_to_string(
400                environment_dir_path.join("ordinaryd.json"),
401            )?)?;
402
403            let data_path = environment_dir_path.join("data");
404
405            fs::create_dir_all(&data_path)?;
406
407            let ps = page_size::get();
408
409            // round up to full OS page
410            let remainder = storage_size % ps;
411            let mapsize = (storage_size - remainder) + ps;
412
413            tracing::info!(mapsize = %bytesize::ByteSize(mapsize as u64).display().si_short());
414
415            let env = Arc::new(unsafe {
416                let mut env_builder = EnvBuilder::new()?;
417                env_builder.set_maxreaders(126)?;
418                env_builder.set_mapsize(mapsize)?;
419                env_builder.set_maxdbs(13)?;
420                env_builder.open(
421                    match data_path.to_str() {
422                        Some(v) => v,
423                        None => return Err("data_path not a str".into()),
424                    },
425                    &saferlmdb::open::Flags::empty(),
426                    0o600,
427                )?
428            });
429
430            let keys_dir = environment_dir_path.join("keys");
431            fs::create_dir_all(&keys_dir)?;
432
433            let auth_key_path = keys_dir.join("auth");
434
435            let auth_key: [u8; 32] = if auth_key_path.exists() && auth_key_path.is_file() {
436                let auth_key = fs::read(&auth_key_path)?;
437                let auth_key: [u8; 32] = auth_key[..].try_into()?;
438                auth_key
439            } else {
440                let mut auth_key = [0u8; 32];
441                let mut rng = rand_chacha::ChaCha20Rng::try_from_rng(&mut SysRng)?;
442
443                rng.fill_bytes(&mut auth_key[..]);
444
445                let mut auth_key_file = File::create(auth_key_path)?;
446                auth_key_file.write_all(&auth_key)?;
447
448                auth_key
449            };
450
451            let auth = Arc::new(Auth::new(
452                domain.into(),
453                Some(AuthConfig {
454                    password: PasswordConfig {
455                        protocol: ordinary_config::PasswordProtocol::Opaque,
456                    },
457                    mfa: MfaConfig {
458                        totp: TotpConfig {
459                            template: None,
460                            algorithm: ordinary_config::TotpAlgorithm::Sha1,
461                        },
462                    },
463                    hmac_token: HmacTokenConfig {
464                        algorithm: HmacTokenAlgorithm::HmacBlake2b256,
465                        rotation: 60 * 60 * 24 * 7,
466                    },
467                    signed_token: SignedTokenConfig {
468                        algorithm: SignedTokenAlgorithm::DsaEd25519,
469                        rotation: 60 * 60 * 24 * 30,
470                    },
471                    refresh_token: RefreshTokenConfig {
472                        lifetime: 60 * 60 * 24 * 7,
473                    },
474                    access_token: AccessTokenConfig {
475                        lifetime: 60 * 60 * 24,
476                        claims: api_account_claims(),
477                    },
478                    client_hash: ordinary_config::ClientPasswordHash::Sha256,
479                    cookies_enabled: true,
480                    invite: Some(InviteConfig {
481                        mode: InviteMode::Viral,
482                        lifetime: 60 * 60 * 24,
483                        clean_interval: (30, 90),
484                        claims: Some(api_invite_claims()),
485                    }),
486                }),
487                auth_key,
488                env.clone(),
489            )?);
490
491            let apps_dir = environment_dir_path.join("apps");
492            fs::create_dir_all(&apps_dir)?;
493
494            Ok(OrdinaryApiServer {
495                config,
496                auth,
497                _env: env,
498                apps: Arc::new(RwLock::new(HashMap::new())),
499                apps_dir,
500                log_manager,
501            })
502        })
503    }
504
505    #[allow(
506        clippy::too_many_arguments,
507        clippy::fn_params_excessive_bools,
508        clippy::too_many_lines
509    )]
510    pub async fn start<P, F>(
511        &self,
512        server_span: Span,
513        mode: SecurityMode<P>,
514        listener: TcpListener,
515        secure_cookies: bool,
516        log_headers: bool,
517        log_ips: bool,
518        log_size: bool,
519        api_domain: String,
520        api_contacts: &[String],
521        redirect_listener: Option<TcpListener>,
522        dedicated_ports: bool,
523        log_index: bool,
524        redacted_hash: Option<RedactedHashAlg>,
525        swagger: bool,
526        signal: fn() -> F,
527    ) -> Result<(), Box<dyn Error>>
528    where
529        P: AsRef<Path>,
530        F: Future<Output = ()> + Send + 'static,
531    {
532        use axum::extract::State;
533
534        let start_span = tracing::info_span!(
535            "start",
536            env = &self.config.env_name,
537            pid = process::id(),
538            domain = api_domain
539        );
540
541        let address = SocketAddr::from((self.config.public_dns_ip, 53));
542        let conn = UdpClientStream::builder(address, TokioRuntimeProvider::default()).build();
543
544        let (client, bg) = Client::connect(conn).await?;
545        tokio::spawn(bg);
546
547        let server_span_clone = server_span.clone();
548        let server_span_clone2 = server_span.clone();
549        let server_span_clone3 = server_span.clone();
550
551        let api_domain_clone = api_domain.clone();
552
553        let limits = serde_json::to_string(&self.config.limits)?;
554
555        let state = start_span.in_scope(|| {
556            Arc::new(OrdinaryApiServerState {
557                domain: api_domain.clone(),
558                secure: !matches!(mode, SecurityMode::Insecure),
559                secure_cookies,
560                log_headers,
561                log_ips,
562                log_size,
563                auth: self.auth.clone(),
564                apps: self.apps.clone(),
565                apps_dir: self.apps_dir.clone(),
566                dbs: Arc::new(Mutex::new(HashMap::new())),
567                env_name: self.config.env_name.clone(),
568                provision_mode: match &mode {
569                    SecurityMode::Insecure => None,
570                    SecurityMode::Secure(_, provision) => Some(provision.clone()),
571                },
572                dedicated_ports,
573                server_span: server_span.clone(),
574                log_manager: self.log_manager.clone(),
575                log_index,
576                limits,
577                config: self.config.clone(),
578
579                signal_tx: Arc::new(RwLock::new(None)),
580                close_rx: Arc::new(RwLock::new(None)),
581
582                dns_client: Arc::new(tokio::sync::Mutex::new(client)),
583            })
584        });
585
586        let request_id = HeaderName::from_static(REQUEST_ID_HEADER);
587
588        let redacted_hash = if let Some(redacted_hash) = redacted_hash {
589            Arc::new(Some(WrappedRedactedHashingAlg(redacted_hash)))
590        } else {
591            Arc::new(None)
592        };
593
594        let redacted_hash_clone = redacted_hash.clone();
595        let redacted_hash_clone1 = redacted_hash.clone();
596
597        let last_modified = UtcDateTime::now();
598
599        let last_modified_string = last_modified.format(&GMT_FORMAT)?;
600        let last_modified_header = HeaderValue::from_str(last_modified_string.as_str())?;
601
602        let ordinaryd_resource_layers = ServiceBuilder::new()
603            .layer(axum::middleware::from_fn(
604                move |req_headers: HeaderMap, request: axum::extract::Request, next: Next| {
605                    ordinary_utils::middleware::http_cache_middleware(
606                        last_modified,
607                        req_headers,
608                        request,
609                        next,
610                    )
611                },
612            ))
613            .layer(SetResponseHeaderLayer::if_not_present(
614                header::LAST_MODIFIED,
615                last_modified_header.clone(),
616            ))
617            .layer(SetResponseHeaderLayer::if_not_present(
618                header::EXPIRES,
619                |_: &Response<_>| {
620                    let future = UtcDateTime::now() + time::Duration::minutes(10);
621
622                    if let Ok(formatted) = future.format(&GMT_FORMAT)
623                        && let Ok(expires) = HeaderValue::from_str(formatted.as_str())
624                    {
625                        return Some(expires);
626                    }
627
628                    None
629                },
630            ))
631            .layer(SetResponseHeaderLayer::if_not_present(
632                header::VARY,
633                HeaderValue::from_static(header::ACCEPT_ENCODING.as_str()),
634            ));
635
636        let mut service = Router::new();
637
638        if swagger {
639            // todo: rapidoc
640            // todo: scalar
641            // todo: redoc
642
643            service = service
644                .merge(
645                    SwaggerUi::new("/.ordinary/swagger").config(Config::from("/.ordinary/openapi")),
646                )
647                .route_layer(ordinaryd_resource_layers.clone().layer(
648                    SetResponseHeaderLayer::if_not_present(
649                        header::CONTENT_SECURITY_POLICY,
650                        HeaderValue::from_static(
651                            "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src https://img.shields.io",
652                        ),
653                    ),
654                ));
655        }
656
657        // todo: || openapi
658        if swagger {
659            let api = ApiDoc::openapi().to_json()?;
660
661            service = service.route(
662                "/.ordinary/openapi",
663                get(|| async move {
664                    (
665                        StatusCode::OK,
666                        [(header::CONTENT_TYPE, "application/json")],
667                        api,
668                    )
669                })
670                .route_layer(ordinaryd_resource_layers.clone()),
671            );
672        }
673
674        service = service.route(
675            "/.ordinary/limits",
676            get(
677                |State(state): State<Arc<OrdinaryApiServerState>>| async move {
678                    (
679                        StatusCode::OK,
680                        [(header::CONTENT_TYPE, "application/json")],
681                        state.limits.clone(),
682                    )
683                },
684            )
685            .route_layer(ordinaryd_resource_layers.clone()),
686        );
687
688        let mut service = service
689            .route("/healthz", get(|| async { StatusCode::OK }))
690            // auth ops
691            .route(
692                "/v1/accounts/registration/start",
693                put(ops::auth::registration_start),
694            )
695            .route(
696                "/v1/accounts/registration/finish",
697                put(ops::auth::registration_finish),
698            )
699            .route("/v1/accounts/login/start", put(ops::auth::login_start))
700            .route("/v1/accounts/login/finish", put(ops::auth::login_finish))
701            .route("/v1/accounts/access/get", put(ops::auth::access_get))
702            .route("/v1/accounts/logout", put(ops::auth::logout))
703            // reset password
704            .route(
705                "/v1/accounts/password/reset/login/start",
706                put(ops::auth::password_reset_login_start),
707            )
708            .route(
709                "/v1/accounts/password/reset/login/finish",
710                put(ops::auth::password_reset_login_finish),
711            )
712            .route(
713                "/v1/accounts/password/reset/registration/start",
714                put(ops::auth::password_reset_registration_start),
715            )
716            .route(
717                "/v1/accounts/password/reset/registration/finish",
718                put(ops::auth::password_reset_registration_finish),
719            )
720            // forgot password
721            .route(
722                "/v1/accounts/password/forgot/start",
723                put(ops::auth::password_forgot_start),
724            )
725            .route(
726                "/v1/accounts/password/forgot/finish",
727                put(ops::auth::password_forgot_finish),
728            )
729            // reset totp mfa
730            .route(
731                "/v1/accounts/mfa/totp/reset/start",
732                put(ops::auth::mfa_reset_totp_start),
733            )
734            .route(
735                "/v1/accounts/mfa/totp/reset/finish",
736                put(ops::auth::mfa_reset_totp_finish),
737            )
738            // lost totp mfa
739            .route(
740                "/v1/accounts/mfa/totp/lost/start",
741                put(ops::auth::mfa_lost_totp_start),
742            )
743            .route(
744                "/v1/accounts/mfa/totp/lost/finish",
745                put(ops::auth::mfa_lost_totp_finish),
746            )
747            // delete account
748            .route(
749                "/v1/accounts/delete/start",
750                put(ops::auth::account_delete_start),
751            )
752            .route(
753                "/v1/accounts/delete/finish",
754                put(ops::auth::account_delete_finish),
755            )
756            // reset recovery codes
757            .route(
758                "/v1/accounts/recovery-codes/reset/start",
759                put(ops::auth::recovery_reset_codes_start),
760            )
761            .route(
762                "/v1/accounts/recovery-codes/reset/finish",
763                put(ops::auth::recovery_reset_codes_finish),
764            )
765            // root ops
766            .route("/v1/apps", get(ops::app::list))
767            .route("/v1/logs", get(ops::logs::root))
768            // admin ops
769            .route("/v1/accounts/invite", put(ops::accounts::invite))
770            .route("/v1/accounts", get(ops::accounts::list))
771            .route("/v1/accounts/delete", put(ops::accounts::delete))
772            // app account ops
773            .route("/v1/app/accounts/invite", put(ops::app::invite))
774            .route("/v1/app/accounts", get(ops::app::accounts_list))
775            // app ops
776            .route("/v1/app/deploy", post(ops::app::deploy))
777            .route("/v1/app/patch", patch(ops::app::patch))
778            .route("/v1/app/migrate", put(ops::app::migrate))
779            .route("/v1/app/bridge", put(ops::app::bridge))
780            .route("/v1/app/kill", put(ops::app::kill))
781            .route("/v1/app/erase", put(ops::app::erase))
782            .route("/v1/app/logs", get(ops::logs::app))
783            // template ops
784            .route("/v1/templates", put(ops::templates::upload))
785            // content ops
786            .route("/v1/content", put(ops::content::update))
787            // assets ops
788            .route("/v1/assets", put(ops::assets::write))
789            // action ops
790            .route("/v1/actions", put(ops::actions::install))
791            // models ops
792            .route("/v1/models/items", get(ops::models::items_list))
793            .fallback(|| async { StatusCode::NOT_FOUND })
794            .with_state(state.clone())
795            .layer(
796                ServiceBuilder::new()
797                    .layer(CatchPanicLayer::custom(response_for_panic))
798                    .layer(RequestDecompressionLayer::new())
799                    .layer(CompressionLayer::new()),
800            )
801            .layer(
802                ServiceBuilder::new()
803                    .layer(SetRequestIdLayer::new(request_id.clone(), MakeRequestUuid))
804                    .layer(
805                        TraceLayer::new_for_http()
806                            .make_span_with(move |req: &Request<_>| {
807                                let request_id = req.headers().get(REQUEST_ID_HEADER);
808
809                                let ip = log_ips.then(|| {
810                                    req.extensions()
811                                        .get::<ConnectInfo<SocketAddr>>()
812                                        .map(|addr| tracing::field::display(addr.ip()))
813                                });
814
815                                let query = req.uri().query().map(tracing::field::display);
816
817                                server_span_clone.in_scope(|| match request_id {
818                                    Some(rid) => {
819                                        tracing::info_span!(
820                                            "admin",
821                                            domain = %api_domain_clone,
822                                            id = %rid
823                                                .to_str()
824                                                .unwrap_or(Uuid::new_v4().to_string().as_str()),
825                                            ip,
826                                            path = %req.uri().path(),
827                                            query,
828                                        )
829                                    }
830                                    None => {
831                                        tracing::info_span!(
832                                            "admin",
833                                            domain = %api_domain_clone,
834                                            id = %Uuid::new_v4(),
835                                            ip,
836                                            path = %req.uri().path(),
837                                            query,
838                                        )
839                                    }
840                                })
841                            })
842                            .on_request(move |req: &Request<_>, _: &Span| {
843                                let hd = log_headers
844                                    .then_some(HeadersDebug(req.headers(), redacted_hash.clone()));
845
846                                #[cfg(tracing_unstable)]
847                                let headers = log_headers.then_some(tracing::field::valuable(&hd));
848
849                                #[cfg(not(tracing_unstable))]
850                                let headers = log_headers.then_some(tracing::field::debug(&hd));
851
852                                tracing::info!(
853                                    version = ?req.version(),
854                                    method = %req.method(),
855                                    headers,
856                                    "req"
857                                );
858                            })
859                            .on_response(move |res: &Response<_>, latency: Duration, _: &Span| {
860                                let hd = log_headers.then_some(HeadersDebug(
861                                    res.headers(),
862                                    redacted_hash_clone.clone(),
863                                ));
864
865                                #[cfg(tracing_unstable)]
866                                let headers = log_headers.then_some(tracing::field::valuable(&hd));
867
868                                #[cfg(not(tracing_unstable))]
869                                let headers = log_headers.then_some(tracing::field::debug(&hd));
870
871                                let status = res.status().as_u16();
872                                let latency = LatencyDisplay(latency.as_nanos() as f64);
873
874                                if status >= 500 {
875                                    tracing::error!(status, headers, %latency, "res");
876                                } else if status >= 400 {
877                                    tracing::warn!(status, headers, %latency, "res");
878                                } else {
879                                    tracing::info!(status, headers, %latency, "res");
880                                }
881                            })
882                            .on_failure(
883                                |error: ServerErrorsFailureClass, _: Duration, _: &Span| {
884                                    tracing::error!(
885                                        err = %error,
886                                        "fail"
887                                    );
888                                },
889                            ),
890                    )
891                    .layer(TimeoutLayer::with_status_code(
892                        StatusCode::REQUEST_TIMEOUT,
893                        Duration::from_secs(10),
894                    ))
895                    .layer(PropagateRequestIdLayer::new(request_id))
896                    .layer(SetResponseHeaderLayer::if_not_present(
897                        header::SERVER,
898                        HeaderValue::from_static("Ordinary"),
899                    ))
900                    .layer(SetResponseHeaderLayer::overriding(
901                        header::ETAG,
902                        modify_etag_for_encoding,
903                    )),
904            );
905
906        if state.secure {
907            service = service.layer(SetResponseHeaderLayer::if_not_present(
908                header::STRICT_TRANSPORT_SECURITY,
909                HeaderValue::from_static("max-age=31536000"),
910            ));
911        }
912
913        if let Some(listener) = redirect_listener {
914            let api_domain = api_domain.clone();
915
916            let request_id_header = HeaderName::from_static(REQUEST_ID_HEADER);
917
918            let local_addr = listener.local_addr()?;
919            let state_clone = state.clone();
920
921            tokio::spawn(async move {
922                use axum::extract::State;
923
924                let span = server_span_clone3;
925
926                span.in_scope(
927                    || tracing::info!(server = %"redirect", addr = %local_addr, "listening"),
928                );
929
930                let handler = async move |uri: Uri,
931                                          headers: HeaderMap,
932                                          State(state): State<Arc<OrdinaryApiServerState>>|
933                            -> Result<Redirect, StatusCode> {
934                    // todo: handle localhost redirecting
935
936                    if let Some(host) = get_host(&headers, &uri) {
937                        if host == api_domain
938                            && let Some(path_and_query) = uri.path_and_query()
939                        {
940                            return Ok(Redirect::permanent(&format!(
941                                "https://{api_domain}{}",
942                                path_and_query.as_str()
943                            )));
944                        }
945
946                        let apps = state.apps.read();
947
948                        if apps.contains_key(&host)
949                            && let Some(path_and_query) = uri.path_and_query()
950                        {
951                            return Ok(Redirect::permanent(&format!(
952                                "https://{host}{}",
953                                path_and_query.as_str()
954                            )));
955                        }
956                    }
957
958                    Err(StatusCode::NOT_FOUND)
959                };
960
961                let service = redirect_service(
962                    span,
963                    redacted_hash_clone1,
964                    log_ips,
965                    log_headers,
966                    request_id_header,
967                    handler,
968                    state_clone,
969                );
970
971                if let Err(err) = axum::serve(
972                    listener,
973                    service.into_make_service_with_connect_info::<SocketAddr>(),
974                )
975                .with_graceful_shutdown(shutdown_signal())
976                .await
977                {
978                    tracing::error!(%err, "server closed");
979                }
980            });
981        }
982
983        let local_addr = listener.local_addr()?;
984
985        let span = server_span_clone2;
986
987        self.start_apps(&span, &state)?;
988
989        match mode {
990            SecurityMode::Insecure => {
991                span.in_scope(|| {
992                    tracing::info!(
993                        server = %"primary",
994                        addr = %local_addr,
995                        provision = %"none",
996                        "listening"
997                    );
998                });
999
1000                axum::serve(
1001                    listener,
1002                    service.into_make_service_with_connect_info::<SocketAddr>(),
1003                )
1004                .with_graceful_shutdown(signal())
1005                .await?;
1006            }
1007            SecurityMode::Secure(cert_dir_path, provision) => {
1008                let span_clone = span.clone();
1009
1010                let (signal_tx, signal_rx) = watch::channel(());
1011                let (close_tx, close_rx) = watch::channel(());
1012
1013                {
1014                    let mut signal_tx_lock = state.signal_tx.write();
1015                    *signal_tx_lock = Some(signal_tx.clone());
1016
1017                    let mut close_rx_lock = state.close_rx.write();
1018                    *close_rx_lock = Some(close_rx.clone());
1019                }
1020
1021                let signal_tx_clone = state.signal_tx.clone();
1022
1023                tokio::spawn(async move {
1024                    signal().await;
1025
1026                    span_clone.in_scope(|| {
1027                        tracing::warn!("graceful shutdown");
1028                    });
1029
1030                    {
1031                        let mut signal_tx_lock = signal_tx_clone.write();
1032                        *signal_tx_lock = None;
1033                    }
1034
1035                    drop(signal_rx);
1036                });
1037
1038                match provision {
1039                    ProvisionMode::Localhost => {
1040                        let cert_dir_path = cert_dir_path.as_ref();
1041
1042                        ordinary_utils::generate_self_signed_localhost_certs(cert_dir_path)?;
1043
1044                        let rustls_config = rustls_server_config(
1045                            cert_dir_path.join("key.pem"),
1046                            cert_dir_path.join("crt.pem"),
1047                        )?;
1048
1049                        let tls_acceptor = TlsAcceptor::from(rustls_config);
1050                        span.in_scope(|| {
1051                            tracing::info!(
1052                                server = %"primary",
1053                                addr = %local_addr,
1054                                provision = %"localhost",
1055                                "listening"
1056                            );
1057                        });
1058
1059                        loop {
1060                            let span = span.clone();
1061                            let (cnx, addr) = tokio::select! {
1062                                conn = listener.accept() => conn?,
1063                                () = signal_tx.closed() => {
1064                                    span.in_scope(|| {
1065                                       tracing::warn!("not accepting new connections");
1066                                    });
1067                                    break;
1068                                }
1069                            };
1070
1071                            let tower_service = service.clone();
1072                            let tls_acceptor = tls_acceptor.clone();
1073
1074                            let signal_tx = signal_tx.clone();
1075                            let close_rx = close_rx.clone();
1076
1077                            tokio::spawn(async move {
1078                                // todo: determine a better timeout
1079                                match timeout(Duration::from_secs(3), tls_acceptor.accept(cnx))
1080                                    .await
1081                                {
1082                                    Ok(stream) => {
1083                                        let stream = match stream {
1084                                            Ok(stream) => TokioIo::new(stream),
1085                                            Err(err) => {
1086                                                span.in_scope(|| {
1087                                                    if log_ips {
1088                                                        tracing::warn!(ip = %addr.ip(), port = addr.port(), %err, "accept");
1089                                                    } else {
1090                                                        tracing::warn!(%err, "accept");
1091                                                    }
1092                                                });
1093
1094                                                return;
1095                                            }
1096                                        };
1097
1098                                        Self::handle_stream(
1099                                            &span,
1100                                            tower_service,
1101                                            addr,
1102                                            stream,
1103                                            &signal_tx,
1104                                            &close_rx,
1105                                        )
1106                                        .await;
1107                                    }
1108                                    Err(_) => {
1109                                        span.in_scope(|| {
1110                                            if log_ips {
1111                                                tracing::warn!(ip = %addr.ip(), port = addr.port(), "timeout");
1112                                            } else {
1113                                                tracing::warn!("timeout");
1114                                            }
1115                                        });
1116                                    }
1117                                }
1118                            });
1119                        }
1120                    }
1121                    ProvisionMode::Staging | ProvisionMode::Production => {
1122                        let contacts = api_contacts
1123                            .iter()
1124                            .map(|c| format!("mailto:{c}"))
1125                            .collect::<Vec<String>>();
1126
1127                        let cache_path = Path::new(cert_dir_path.as_ref()).join("cache");
1128
1129                        let state = AcmeConfig::new([&api_domain])
1130                            .contact(contacts)
1131                            .cache_option(Some(DirCache::new(cache_path)))
1132                            .directory_lets_encrypt(provision == ProvisionMode::Production)
1133                            .state();
1134
1135                        let challenge_rustls_config = state.challenge_rustls_config();
1136                        let mut default_rustls_config =
1137                            ServerConfig::builder_with_provider(Arc::new(ring::default_provider()))
1138                                .with_safe_default_protocol_versions()?
1139                                .with_no_client_auth()
1140                                .with_cert_resolver(state.resolver());
1141
1142                        default_rustls_config.alpn_protocols =
1143                            vec![b"h2".to_vec(), b"http/1.1".to_vec()];
1144
1145                        let default_rustls_config = Arc::new(default_rustls_config);
1146
1147                        let api_domain_clone = api_domain.clone();
1148                        let span_clone = span.clone();
1149
1150                        let provision_str = match provision {
1151                            ProvisionMode::Production => "production",
1152                            ProvisionMode::Staging => "staging",
1153                            _ => unreachable!(),
1154                        };
1155
1156                        let acme_span = span_clone.in_scope(|| {
1157                            tracing::info_span!(
1158                                "acme",
1159                                domain = %api_domain_clone,
1160                                provision = %provision_str
1161                            )
1162                        });
1163
1164                        ordinary_utils::acme_task(acme_span.clone(), state, signal_tx.clone());
1165
1166                        span.in_scope(|| {
1167                            tracing::info!(
1168                                server = %"primary",
1169                                addr = %local_addr,
1170                                provision = %provision_str,
1171                                "listening"
1172                            );
1173                        });
1174
1175                        let api_domain = Arc::new(api_domain);
1176
1177                        loop {
1178                            let span = span.clone();
1179                            let (cnx, addr) = tokio::select! {
1180                                conn = listener.accept() => conn?,
1181                                () = signal_tx.closed() => {
1182                                    span.in_scope(|| {
1183                                       tracing::warn!("not accepting new connections");
1184                                    });
1185                                    break;
1186                                }
1187                            };
1188
1189                            let tower_service = service.clone();
1190                            let acme_span = acme_span.clone();
1191
1192                            let challenge_rustls_config = challenge_rustls_config.clone();
1193                            let default_rustls_config = default_rustls_config.clone();
1194
1195                            let api_domain = api_domain.clone();
1196                            let apps = self.apps.clone();
1197
1198                            let signal_tx = signal_tx.clone();
1199                            let close_rx = close_rx.clone();
1200
1201                            tokio::spawn(async move {
1202                                // todo: determine a better timeout
1203                                match timeout(
1204                                    Duration::from_secs(3),
1205                                    LazyConfigAcceptor::new(Acceptor::default(), cnx),
1206                                )
1207                                .await
1208                                {
1209                                    Ok(start_handshake) => {
1210                                        let start_handshake = match start_handshake {
1211                                            Ok(start_handshake) => start_handshake,
1212                                            Err(err) => {
1213                                                if log_ips {
1214                                                    let tls_span = span.in_scope(|| { tracing::error_span!("tls", ip = %addr.ip(), port = addr.port()) });
1215
1216                                                    tls_span.in_scope(
1217                                                        || tracing::error!(%err, "accept"),
1218                                                    );
1219                                                } else {
1220                                                    let tls_span = span
1221                                                        .in_scope(|| tracing::error_span!("tls"));
1222
1223                                                    tls_span.in_scope(
1224                                                        || tracing::error!(%err, "accept"),
1225                                                    );
1226                                                }
1227
1228                                                return;
1229                                            }
1230                                        };
1231
1232                                        if let Some(sni) =
1233                                            start_handshake.client_hello().server_name()
1234                                        {
1235                                            let tls_span = span.in_scope(|| {
1236                                                if log_ips {
1237                                                    tracing::info_span!("tls", %sni, ip = %addr.ip(), port = addr.port())
1238                                                } else {
1239                                                    tracing::info_span!("tls", %sni)
1240                                                }
1241                                            });
1242
1243                                            if sni == *api_domain {
1244                                                if let Err(err) = Self::handle_handshake(
1245                                                    &tls_span,
1246                                                    &acme_span,
1247                                                    start_handshake,
1248                                                    tower_service,
1249                                                    addr,
1250                                                    challenge_rustls_config,
1251                                                    default_rustls_config,
1252                                                    &signal_tx,
1253                                                    &close_rx,
1254                                                )
1255                                                .await
1256                                                {
1257                                                    tls_span.in_scope(|| {
1258                                                        tracing::error!(%err, "handshake");
1259                                                    });
1260                                                }
1261                                            } else {
1262                                                let apps = apps.read();
1263
1264                                                if let Some(app) = apps.get(sni) {
1265                                                    if let Err(err) = app.stream_tx.send((
1266                                                        start_handshake,
1267                                                        addr,
1268                                                        tls_span.clone(),
1269                                                    )) {
1270                                                        tls_span.in_scope(
1271                                                            || tracing::error!(%err, "handoff"),
1272                                                        );
1273                                                    }
1274                                                } else {
1275                                                    tls_span
1276                                                        .in_scope(|| tracing::warn!("no match"));
1277                                                }
1278                                            }
1279                                        } else {
1280                                            let tls_span = span.in_scope(|| {
1281                                                if log_ips {
1282                                                    tracing::info_span!("tls", ip = %addr.ip(), port = addr.port())
1283                                                } else {
1284                                                    tracing::info_span!("tls")
1285                                                }
1286                                            });
1287
1288                                            tls_span.in_scope(|| tracing::warn!("no sni"));
1289                                        }
1290                                    }
1291                                    Err(_) => {
1292                                        if log_ips {
1293                                            let tls_span = span.in_scope(|| { tracing::error_span!("tls", ip = %addr.ip(), port = addr.port()) });
1294
1295                                            tls_span.in_scope(|| tracing::error!("timeout"));
1296                                        } else {
1297                                            let tls_span =
1298                                                span.in_scope(|| tracing::error_span!("tls"));
1299
1300                                            tls_span.in_scope(|| tracing::error!("timeout"));
1301                                        }
1302                                    }
1303                                }
1304                            });
1305                        }
1306                    }
1307                }
1308
1309                {
1310                    let mut close_rx_lock = state.close_rx.write();
1311                    *close_rx_lock = None;
1312                }
1313
1314                drop(close_rx);
1315                drop(listener);
1316
1317                span.in_scope(|| {
1318                    tracing::warn!(
1319                        "waiting for {} task(s) to finish",
1320                        close_tx.receiver_count()
1321                    );
1322                });
1323                close_tx.closed().await;
1324            }
1325        }
1326
1327        Ok(())
1328    }
1329
1330    async fn handle_stream(
1331        span: &Span,
1332        tower_service: Router,
1333        addr: SocketAddr,
1334        stream: TokioIo<TlsStream<TcpStream>>,
1335        signal_tx: &Sender<()>,
1336        close_rx: &Receiver<()>,
1337    ) {
1338        let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| {
1339            let mut tower_service = Extension(ConnectInfo(addr)).layer(tower_service.clone());
1340            tower_service.call(request)
1341        });
1342
1343        let mut builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
1344        builder.http2().enable_connect_protocol();
1345
1346        let signal_tx = signal_tx.clone();
1347        let close_rx = close_rx.clone();
1348
1349        let mut conn = pin!(builder.serve_connection_with_upgrades(stream, hyper_service));
1350        let mut signal_closed = pin!(signal_tx.closed().fuse());
1351
1352        loop {
1353            tokio::select! {
1354                result = conn.as_mut() => {
1355                    span.in_scope(|| {
1356                        match result {
1357                            Ok(()) => span.in_scope(|| tracing::info!("closed")),
1358                            Err(err) => span.in_scope(|| tracing::warn!(%err, "connection")),
1359                        }
1360                    });
1361
1362                    break;
1363                }
1364                () = &mut signal_closed => {
1365                    span.in_scope(|| {
1366                        tracing::warn!("shutdown");
1367                    });
1368                    conn.as_mut().graceful_shutdown();
1369                }
1370            }
1371        }
1372
1373        drop(close_rx);
1374    }
1375
1376    #[allow(clippy::too_many_arguments)]
1377    pub async fn handle_handshake(
1378        tls_span: &Span,
1379        acme_span: &Span,
1380        start_handshake: StartHandshake<TcpStream>,
1381        tower_service: Router,
1382        addr: SocketAddr,
1383        challenge_rustls_config: Arc<ServerConfig>,
1384        default_rustls_config: Arc<ServerConfig>,
1385        signal_tx: &Sender<()>,
1386        close_rx: &Receiver<()>,
1387    ) -> Result<(), Box<dyn Error + Send + Sync>> {
1388        if is_tls_alpn_challenge(&start_handshake.client_hello()) {
1389            acme_span.in_scope(|| tracing::info!(evt = %"validation", "TLS-ALPN-01"));
1390            let mut tls = start_handshake.into_stream(challenge_rustls_config).await?;
1391
1392            tls.shutdown().await?;
1393        } else {
1394            tls_span.in_scope(|| tracing::info!("connecting"));
1395            let as_stream = start_handshake.into_stream(default_rustls_config).await?;
1396
1397            let stream = TokioIo::new(as_stream);
1398
1399            Self::handle_stream(tls_span, tower_service, addr, stream, signal_tx, close_rx).await;
1400        }
1401
1402        Ok(())
1403    }
1404
1405    fn start_apps(
1406        &self,
1407        server_span: &Span,
1408        state: &Arc<OrdinaryApiServerState>,
1409    ) -> Result<(), Box<dyn Error>> {
1410        let state_clone = state.clone();
1411
1412        let Ok(shared_rt) = tokio::runtime::Handle::try_current() else {
1413            return Err("failed to get shared runtime".into());
1414        };
1415
1416        server_span.in_scope(|| -> Result<(), Box<dyn Error>> {
1417            // todo: sleep this for 2 seconds or something to wait for the server to get started up
1418
1419            let mut apps = state_clone.apps.write();
1420
1421            for entry in fs::read_dir(&self.apps_dir)?.flatten() {
1422                let config_path = entry.path().join("ordinary.json");
1423
1424                match fs::read_to_string(&config_path) {
1425                    Ok(config_str) => match serde_json::from_str::<OrdinaryConfig>(&config_str) {
1426                        Ok(config) => {
1427                            let domain = config.domain.clone();
1428                            let app_span = tracing::info_span!("app", %domain);
1429
1430                            match ops::app::dbs(&state_clone, &config, &entry.path(), Some(app_span.clone())) {
1431                                Ok((auth, storage)) => {
1432                                    match ops::app::start(
1433                                        &state_clone,
1434                                        &config,
1435                                        auth,
1436                                        storage,
1437                                        match state_clone
1438                                            .provision_mode
1439                                            .as_ref()
1440                                            .unwrap_or(&ProvisionMode::Localhost)
1441                                        {
1442                                            ProvisionMode::Localhost => {
1443                                                ordinary_app::server::ProvisionMode::Localhost
1444                                            }
1445                                            ProvisionMode::Staging => {
1446                                                ordinary_app::server::ProvisionMode::Staging
1447                                            }
1448                                            ProvisionMode::Production => {
1449                                                ordinary_app::server::ProvisionMode::Production
1450                                            }
1451                                        },
1452                                        entry.path().join("certs"),
1453                                        shared_rt.clone(),
1454                                        Some(app_span),
1455                                    ) {
1456                                        Ok((port, app, kill_tx, stream_tx)) => {
1457                                            let cnames = app.config.cnames.clone();
1458
1459                                            let app = Arc::new(WrappedOrdinaryAppServer {
1460                                                port,
1461                                                app,
1462                                                kill_tx,
1463                                                stream_tx,
1464                                            });
1465
1466                                            if let Some(cnames) = cnames {
1467                                                for cname in cnames {
1468                                                    apps.insert(cname, app.clone());
1469                                                }
1470                                            }
1471
1472                                            apps.insert(
1473                                                domain,
1474                                                app,
1475                                            );
1476                                        }
1477                                        Err(err) => {
1478                                            tracing::error!(%err, domain, "failed to start app");
1479                                        }
1480                                    }
1481                                }
1482                                Err(err) => {
1483                                    tracing::error!(%err, domain, "failed to init dbs");
1484                                }
1485                            }
1486                        }
1487                        Err(err) => {
1488                            if let Some(path_str) = config_path.to_str() {
1489                                tracing::error!(%err, path = path_str, "failed to parse config file");
1490                            } else {
1491                                tracing::error!(%err, "failed to parse config file");
1492                            }
1493                        }
1494                    },
1495                    Err(err) => {
1496                        if let Some(path_str) = config_path.to_str() {
1497                            tracing::error!(%err, path = path_str, "failed to read config file");
1498                        } else {
1499                            tracing::error!(%err, "failed to read config file");
1500                        }
1501                    }
1502                }
1503            }
1504
1505            drop(apps);
1506
1507            Ok(())
1508        })
1509    }
1510}