Skip to main content

ordinary_api/server/
mod.rs

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