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