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