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::service::OrdinaryMonitorService;
18use ordinary_storage::Storage;
19use parking_lot::{Mutex, RwLock};
20use rand_chacha::rand_core::Rng;
21use rand_chacha::rand_core::SeedableRng;
22use saferlmdb::{
23    self as lmdb, Database, DatabaseOptions, EnvBuilder, Environment, ReadTransaction,
24    WriteTransaction, put,
25};
26use sha2::{Digest, Sha256};
27use std::io::Write;
28use std::process;
29use tokio::net::TcpListener;
30use tokio::net::TcpStream;
31use tokio_rustls::LazyConfigAcceptor;
32use tokio_rustls::StartHandshake;
33
34use axum::Router;
35use axum::http::HeaderName;
36use axum::response::Response;
37use axum::routing::{delete, get, post, put};
38
39use axum::http::StatusCode;
40use tower_http::classify::ServerErrorsFailureClass;
41use tower_http::request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer};
42use uuid::Uuid;
43
44use std::net::SocketAddr;
45use std::path::Path;
46use std::path::PathBuf;
47use std::sync::Arc;
48use std::time::Duration;
49
50use rustls_acme::{AcmeConfig, caches::DirCache};
51use tokio_rustls::{TlsAcceptor, rustls::ServerConfig};
52use tower::ServiceBuilder;
53use tower_http::compression::CompressionLayer;
54use tower_http::decompression::RequestDecompressionLayer;
55use tower_http::trace::TraceLayer;
56use tracing::{Instrument, Span};
57
58use ordinary_auth::{Auth, AuthClient, OsRng};
59
60use std::fs;
61
62use crate::{api_account_claims, api_invite_claims};
63use anyhow::bail;
64use axum::middleware::Next;
65use getrandom::SysRng;
66use ordinary_config::{
67    AccessTokenConfig, AuthConfig, InviteConfig, InviteMode, MfaConfig, OrdinaryApiConfig,
68    OrdinaryConfig, PasswordConfig, RedactedHashAlg, RefreshTokenConfig, TotpConfig,
69};
70use ordinary_monitor::tracing::logger::OrdinaryLogger;
71use ordinary_utils::middleware::modify_etag_for_encoding;
72use ordinary_utils::{
73    GMT_FORMAT, HeadersDebug, LatencyDisplay, REQUEST_ID_HEADER, WrappedRedactedHashingAlg,
74    get_bearer_token_as_bytes, get_host, redirect_service, response_for_panic,
75    rustls_server_config, shutdown_signal,
76};
77use sysinfo::{Pid, System};
78use time::UtcDateTime;
79use tokio::sync::watch;
80use tokio::sync::watch::{Receiver, Sender};
81use tokio::time::timeout;
82use tokio_rustls::rustls::crypto::ring;
83use tokio_rustls::rustls::server::Acceptor;
84use tower_http::catch_panic::CatchPanicLayer;
85use tower_http::set_header::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.6.0-pre.13)](https://docs.rs/ordinary-api/0.6.0-pre.13)
105[![dependency status](https://img.shields.io/deps-rs/ordinary-api/0.6.0-pre.13)](https://deps.rs/crate/ordinary-api/0.6.0-pre.13)
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
368type Dbs = Arc<tokio::sync::Mutex<HashMap<String, (Arc<Environment>, Arc<Auth>, Arc<Storage>)>>>;
369type AppServers = Arc<tokio::sync::RwLock<HashMap<String, Arc<WrappedOrdinaryAppServer>>>>;
370
371#[allow(clippy::struct_excessive_bools)]
372pub struct OrdinaryApiServerState {
373    pub domain: String,
374    pub app_domains: Arc<Vec<String>>,
375    pub secure: bool,
376    pub secure_cookies: bool,
377    pub log_headers: bool,
378    pub log_ips: bool,
379    pub log_size: bool,
380    pub auth: Arc<Auth>,
381    pub apps: AppServers,
382    pub apps_dir: PathBuf,
383    pub dbs: Dbs,
384    pub env_name: String,
385    pub provision_mode: Option<ProvisionMode>,
386    pub dedicated_ports: bool,
387    pub server_span: Span,
388    pub monitor: Arc<Option<OrdinaryMonitorService>>,
389    pub stored_logs: bool,
390    pub limits: String,
391    pub config: OrdinaryApiConfig,
392
393    pub signal_tx: Arc<RwLock<Option<Sender<()>>>>,
394    pub close_rx: Arc<RwLock<Option<Receiver<()>>>>,
395
396    pub privileged_domains: HashSet<String>,
397
398    pub pid: Pid,
399    pub system: Arc<Mutex<System>>,
400
401    pub account_lock_manager: Arc<AccountLockManager>,
402}
403
404#[derive(PartialEq, Clone)]
405pub enum ProvisionMode {
406    Localhost,
407    Staging,
408    Production,
409}
410
411pub enum SecurityMode<T: AsRef<Path>> {
412    Insecure,
413    Secure(T, ProvisionMode),
414}
415
416pub struct OrdinaryApiServer {
417    auth: Arc<Auth>,
418    env: Arc<Environment>,
419    config: OrdinaryApiConfig,
420    apps: AppServers,
421    apps_dir: PathBuf,
422    monitor: Arc<Option<OrdinaryMonitorService>>,
423}
424
425#[cfg(feature = "server")]
426impl OrdinaryApiServer {
427    #[allow(clippy::similar_names, clippy::too_many_arguments)]
428    pub async fn init(
429        env_name: &str,
430        domain: &str,
431        password: &str,
432        env_path: impl AsRef<Path>,
433        storage_size: usize,
434        api_contacts: &[String],
435        app_domains: &[String],
436        privileged_domains: &Option<Vec<String>>,
437        logger: Option<OrdinaryLogger>,
438    ) -> anyhow::Result<(String, Vec<u8>)> {
439        let span = tracing::info_span!("init", env = %env_name, pid = process::id());
440
441        let mut limits = OrdinaryApiLimits::default();
442        if let Some(pd) = privileged_domains {
443            pd.clone_into(&mut limits.privileged_domains);
444        }
445        limits.app_domains = app_domains.to_vec();
446
447        async {
448            let config = OrdinaryApiConfig {
449                domain: domain.into(),
450                contacts: api_contacts.to_vec(),
451                // todo: pass in as a flag
452                public_dns_ip: None,
453                env_name: env_name.to_string(),
454                limits,
455            };
456            let config_file = serde_json::to_string_pretty(&config)?;
457
458            let data_path = env_path.as_ref().join("data");
459
460            if data_path.exists() {
461                bail!("environment already initialized");
462            }
463
464            fs::write(env_path.as_ref().join("ordinaryd.json"), config_file)?;
465
466            let api_server =
467                OrdinaryApiServer::new(env_name, env_path, storage_size, logger).await?;
468
469            let mut input = domain.as_bytes().to_vec();
470            input.extend_from_slice(b"root");
471
472            let mut password_input = input.clone();
473            password_input.extend_from_slice(password.as_bytes());
474
475            let mut hasher = Sha256::new();
476            hasher.update(&password_input);
477            let password = hasher.finalize().to_vec();
478
479            let invite_token = api_server.auth.api_invite_get(domain, "root", None)?;
480
481            let (state, reg_start_req) = AuthClient::registration_start_req(b"root", &password)?;
482
483            let checked_claims = api_server.auth.invite_check(&invite_token)?;
484            let reg_start_res = api_server.auth.registration_start(
485                reg_start_req,
486                None,
487                None,
488                Some(checked_claims),
489            )?;
490
491            let (private_key, reg_finish_req) =
492                AuthClient::registration_finish_req(b"root", &password, &state, &reg_start_res)?;
493            let (reg_finish_res, ..) = api_server.auth.registration_finish(reg_finish_req, None)?;
494
495            let (totp, _recovery_codes) = AuthClient::decrypt_totp_mfa(
496                &reg_finish_res,
497                private_key,
498                env_name.to_string(),
499                "root".into(),
500            )?;
501
502            Ok((totp.get_url(), totp.secret))
503        }
504        .instrument(span.clone())
505        .await
506    }
507
508    #[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
509    pub async fn new(
510        env_name: &str,
511        env_path: impl AsRef<Path>,
512        storage_size: usize,
513        logger: Option<OrdinaryLogger>,
514    ) -> anyhow::Result<OrdinaryApiServer> {
515        let span = tracing::info_span!("setup", env = %env_name, pid = process::id());
516
517        async {
518            let config: OrdinaryApiConfig = serde_json::from_str(&fs_err::read_to_string(
519                env_path.as_ref().join("ordinaryd.json"),
520            )?)?;
521
522            let data_path = env_path.as_ref().join("data");
523
524            fs_err::create_dir_all(&data_path)?;
525
526            let ps = page_size::get();
527
528            // round up to full OS page
529            let remainder = storage_size % ps;
530            let mapsize = (storage_size - remainder) + ps;
531
532            tracing::info!(mapsize = %bytesize::ByteSize(mapsize as u64).display().si_short());
533
534            let env = Arc::new(unsafe {
535                let mut env_builder = EnvBuilder::new()?;
536                env_builder.set_maxreaders(126)?;
537                env_builder.set_mapsize(mapsize)?;
538                env_builder.set_maxdbs(13)?;
539                env_builder.open(
540                    match data_path.to_str() {
541                        Some(v) => v,
542                        None => bail!("data_path not a str"),
543                    },
544                    &saferlmdb::open::Flags::empty(),
545                    0o600,
546                )?
547            });
548
549            let keys_dir = env_path.as_ref().join("keys");
550            fs_err::create_dir_all(&keys_dir)?;
551
552            let auth_key_path = keys_dir.join("auth");
553
554            let auth_key: [u8; 32] = if auth_key_path.exists() && auth_key_path.is_file() {
555                let auth_key = fs_err::read(&auth_key_path)?;
556                let auth_key: [u8; 32] = auth_key[..].try_into()?;
557                auth_key
558            } else {
559                let mut auth_key = [0u8; 32];
560                let mut rng = rand_chacha::ChaCha20Rng::try_from_rng(&mut SysRng)?;
561
562                rng.fill_bytes(&mut auth_key[..]);
563
564                let mut auth_key_file = fs_err::File::create(auth_key_path)?;
565                auth_key_file.write_all(&auth_key)?;
566                auth_key_file.flush()?;
567
568                auth_key
569            };
570
571            let auth = Arc::new(Auth::new(
572                config.domain.clone(),
573                Some(AuthConfig {
574                    password: PasswordConfig {
575                        protocol: ordinary_config::PasswordProtocol::Opaque,
576                    },
577                    mfa: MfaConfig {
578                        totp: TotpConfig {
579                            template: None,
580                            algorithm: ordinary_config::TotpAlgorithm::Sha1,
581                        },
582                    },
583                    refresh_token: RefreshTokenConfig::default(),
584                    access_token: AccessTokenConfig {
585                        claims: api_account_claims(),
586                        ..AccessTokenConfig::default()
587                    },
588                    client_hash: ordinary_config::ClientPasswordHash::Sha256,
589                    cookies_enabled: true,
590                    invite: Some(InviteConfig {
591                        // todo: make `InviteMode` configurable
592                        mode: InviteMode::Root,
593                        lifetime: 60 * 60 * 24,
594                        clean_interval: (30, 90),
595                        claims: Some(api_invite_claims()),
596                    }),
597                }),
598                auth_key,
599                env.clone(),
600            )?);
601
602            let apps_dir = env_path.as_ref().join("apps");
603            fs_err::create_dir_all(&apps_dir)?;
604
605            let monitor = Arc::new(logger.map(|logger| {
606                OrdinaryMonitorService::new(logger).expect("failed to set up monitor service")
607            }));
608
609            Ok(OrdinaryApiServer {
610                config,
611                auth,
612                env,
613                apps: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
614                apps_dir,
615                monitor,
616            })
617        }
618        .instrument(span)
619        .await
620    }
621
622    #[allow(
623        clippy::too_many_arguments,
624        clippy::fn_params_excessive_bools,
625        clippy::too_many_lines
626    )]
627    pub async fn start<P, F>(
628        &self,
629        server_span: Span,
630        mode: SecurityMode<P>,
631        listener: TcpListener,
632        secure_cookies: bool,
633        log_headers: bool,
634        log_ips: bool,
635        log_size: bool,
636        redirect_listener: Option<TcpListener>,
637        dedicated_ports: bool,
638        stored_logs: bool,
639        redacted_hash: Option<RedactedHashAlg>,
640        swagger: bool,
641        signal: fn() -> F,
642    ) -> anyhow::Result<()>
643    where
644        P: AsRef<Path>,
645        F: Future<Output = ()> + Send + 'static,
646    {
647        use axum::extract::State;
648
649        let pid = process::id();
650
651        let start_span = tracing::info_span!(
652            "start",
653            env = &self.config.env_name,
654            pid,
655            domain = %self.config.domain
656        );
657
658        let server_span_clone = server_span.clone();
659        let server_span_clone2 = server_span.clone();
660        let server_span_clone3 = server_span.clone();
661
662        let api_domain_clone = self.config.domain.clone();
663
664        let limits = serde_json::to_string(&self.config.limits)?;
665
666        let system = System::new_all();
667
668        let account_lock_manager = Arc::new(AccountLockManager::new(&self.env)?);
669
670        let state = start_span.in_scope(|| {
671            Arc::new(OrdinaryApiServerState {
672                domain: self.config.domain.clone(),
673                app_domains: Arc::new(self.config.limits.app_domains.clone()),
674                secure: !matches!(mode, SecurityMode::Insecure),
675                secure_cookies,
676                log_headers,
677                log_ips,
678                log_size,
679                auth: self.auth.clone(),
680                apps: self.apps.clone(),
681                apps_dir: self.apps_dir.clone(),
682                dbs: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
683                env_name: self.config.env_name.clone(),
684                provision_mode: match &mode {
685                    SecurityMode::Insecure => None,
686                    SecurityMode::Secure(_, provision) => Some(provision.clone()),
687                },
688                dedicated_ports,
689                server_span: server_span.clone(),
690                monitor: self.monitor.clone(),
691                stored_logs,
692                limits,
693                config: self.config.clone(),
694
695                signal_tx: Arc::new(RwLock::new(None)),
696                close_rx: Arc::new(RwLock::new(None)),
697
698                privileged_domains: self
699                    .config
700                    .limits
701                    .privileged_domains
702                    .iter()
703                    .cloned()
704                    .collect::<HashSet<_>>(),
705
706                pid: Pid::from(pid as usize),
707                system: Arc::new(Mutex::new(system)),
708                account_lock_manager,
709            })
710        });
711
712        let request_id = HeaderName::from_static(REQUEST_ID_HEADER);
713
714        let redacted_hash = if let Some(redacted_hash) = redacted_hash {
715            Arc::new(Some(WrappedRedactedHashingAlg(redacted_hash)))
716        } else {
717            Arc::new(None)
718        };
719
720        let redacted_hash_clone = redacted_hash.clone();
721        let redacted_hash_clone1 = redacted_hash.clone();
722
723        let last_modified = UtcDateTime::now();
724
725        let last_modified_string = last_modified.format(&GMT_FORMAT)?;
726        let last_modified_header = HeaderValue::from_str(last_modified_string.as_str())?;
727
728        let ordinaryd_resource_layers = ServiceBuilder::new()
729            .layer(axum::middleware::from_fn(
730                move |req_headers: HeaderMap, request: axum::extract::Request, next: Next| {
731                    ordinary_utils::middleware::http_cache_middleware(
732                        last_modified,
733                        req_headers,
734                        request,
735                        next,
736                    )
737                },
738            ))
739            .layer(SetResponseHeaderLayer::if_not_present(
740                header::LAST_MODIFIED,
741                last_modified_header.clone(),
742            ))
743            .layer(SetResponseHeaderLayer::if_not_present(
744                header::EXPIRES,
745                |_: &Response<_>| {
746                    let future = UtcDateTime::now() + time::Duration::minutes(10);
747
748                    if let Ok(formatted) = future.format(&GMT_FORMAT)
749                        && let Ok(expires) = HeaderValue::from_str(formatted.as_str())
750                    {
751                        return Some(expires);
752                    }
753
754                    None
755                },
756            ))
757            .layer(SetResponseHeaderLayer::if_not_present(
758                header::VARY,
759                HeaderValue::from_static(header::ACCEPT_ENCODING.as_str()),
760            ));
761
762        let mut service = Router::new();
763
764        if swagger {
765            // todo: rapidoc
766            // todo: scalar
767            // todo: redoc
768
769            service = service
770                .merge(
771                    SwaggerUi::new("/.ordinary/swagger").config(Config::from("/.ordinary/openapi")),
772                )
773                .route_layer(ordinaryd_resource_layers.clone().layer(
774                    SetResponseHeaderLayer::if_not_present(
775                        header::CONTENT_SECURITY_POLICY,
776                        HeaderValue::from_static(
777                            "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src https://img.shields.io",
778                        ),
779                    ),
780                ));
781        }
782
783        // todo: || openapi
784        if swagger {
785            let api = ApiDoc::openapi().to_json()?;
786
787            service = service.route(
788                "/.ordinary/openapi",
789                get(|| async move {
790                    (
791                        StatusCode::OK,
792                        [(header::CONTENT_TYPE, "application/json")],
793                        api,
794                    )
795                })
796                .route_layer(ordinaryd_resource_layers.clone()),
797            );
798        }
799
800        service = service.route(
801            "/.ordinary/limits",
802            get(
803                |State(state): State<Arc<OrdinaryApiServerState>>| async move {
804                    (
805                        StatusCode::OK,
806                        [(header::CONTENT_TYPE, "application/json")],
807                        state.limits.clone(),
808                    )
809                },
810            )
811            .route_layer(ordinaryd_resource_layers.clone()),
812        );
813
814        let mut service = service
815            // healthcheck
816            .route("/healthz", get(|| async { StatusCode::OK }))
817            // keys
818            .route("/v1/keys/dh/public", get(ops::keys::dh_public_key))
819            // auth ops
820            .route(
821                "/v1/accounts/registration/start",
822                post(ops::accounts::registration_start),
823            )
824            .route(
825                "/v1/accounts/registration/finish",
826                post(ops::accounts::registration_finish),
827            )
828            .route("/v1/accounts/login/start", post(ops::accounts::login_start))
829            .route(
830                "/v1/accounts/login/finish",
831                post(ops::accounts::login_finish),
832            )
833            .route("/v1/accounts/access", get(ops::accounts::access_get))
834            .route("/v1/accounts/logout", put(ops::accounts::logout))
835            // reset password
836            .route(
837                "/v1/accounts/password/reset/login/start",
838                post(ops::accounts::password_reset_login_start),
839            )
840            .route(
841                "/v1/accounts/password/reset/login/finish",
842                post(ops::accounts::password_reset_login_finish),
843            )
844            .route(
845                "/v1/accounts/password/reset/registration/start",
846                post(ops::accounts::password_reset_registration_start),
847            )
848            .route(
849                "/v1/accounts/password/reset/registration/finish",
850                post(ops::accounts::password_reset_registration_finish),
851            )
852            // forgot password
853            .route(
854                "/v1/accounts/password/forgot/start",
855                post(ops::accounts::password_forgot_start),
856            )
857            .route(
858                "/v1/accounts/password/forgot/finish",
859                post(ops::accounts::password_forgot_finish),
860            )
861            // reset totp mfa
862            .route(
863                "/v1/accounts/mfa/totp/reset/start",
864                post(ops::accounts::mfa_reset_totp_start),
865            )
866            .route(
867                "/v1/accounts/mfa/totp/reset/finish",
868                post(ops::accounts::mfa_reset_totp_finish),
869            )
870            // lost totp mfa
871            .route(
872                "/v1/accounts/mfa/totp/lost/start",
873                post(ops::accounts::mfa_lost_totp_start),
874            )
875            .route(
876                "/v1/accounts/mfa/totp/lost/finish",
877                post(ops::accounts::mfa_lost_totp_finish),
878            )
879            // delete account
880            .route(
881                "/v1/accounts/delete/start",
882                post(ops::accounts::account_delete_start),
883            )
884            .route(
885                "/v1/accounts/delete/finish",
886                post(ops::accounts::account_delete_finish),
887            )
888            // reset recovery codes
889            .route(
890                "/v1/accounts/recovery-codes/reset/start",
891                post(ops::accounts::recovery_reset_codes_start),
892            )
893            .route(
894                "/v1/accounts/recovery-codes/reset/finish",
895                post(ops::accounts::recovery_reset_codes_finish),
896            )
897            // root ops
898            .route("/v1/apps", get(ops::app::list))
899            .route("/v1/logs/metadata", get(ops::root::log_files_metadata))
900            .route("/v1/logs/files/{file}", get(ops::root::log_files))
901            .route("/v1/info", get(ops::root::info))
902            .route("/v1/lock", post(ops::root::lock))
903            .route("/v1/unlock", post(ops::root::unlock))
904            // admin ops
905            .route("/v1/accounts/invite", get(ops::accounts::invite))
906            .route("/v1/accounts", get(ops::accounts::list))
907            .route("/v1/accounts", delete(ops::accounts::delete))
908            // app account ops
909            .route("/v1/app/accounts/invite", get(ops::app::invite))
910            .route("/v1/app/accounts", get(ops::app::accounts_list))
911            // app ops
912            .route("/v1/app/deploy", put(ops::app::deploy))
913            .route("/v1/app/migrate", put(ops::app::migrate))
914            .route("/v1/app/bridge", put(ops::app::bridge))
915            .route("/v1/app/kill", put(ops::app::kill))
916            .route("/v1/app/restart", post(ops::app::restart))
917            .route("/v1/app/erase", delete(ops::app::erase))
918            .route("/v1/app/logs/metadata", get(ops::app::log_files_metadata))
919            .route("/v1/app/logs/files/{file}", get(ops::app::log_files))
920            // template ops
921            .route("/v1/templates", put(ops::templates::upload))
922            // content ops
923            .route("/v1/content", put(ops::content::update))
924            // secret ops
925            .route("/v1/secrets", put(ops::secrets::store))
926            // assets ops
927            .route("/v1/assets", put(ops::assets::write))
928            // action ops
929            .route("/v1/actions", put(ops::actions::install))
930            // models ops
931            .route("/v1/models/items", get(ops::models::items_list))
932            .fallback(|| async { StatusCode::NOT_FOUND })
933            .with_state(state.clone())
934            .layer(
935                ServiceBuilder::new()
936                    .layer(CatchPanicLayer::custom(response_for_panic))
937                    .layer(RequestDecompressionLayer::new())
938                    .layer(CompressionLayer::new()),
939            )
940            .layer(
941                ServiceBuilder::new()
942                    .layer(SetRequestIdLayer::new(request_id.clone(), MakeRequestUuid))
943                    .layer(
944                        TraceLayer::new_for_http()
945                            .make_span_with(move |req: &Request<_>| {
946                                let request_id = req.headers().get(REQUEST_ID_HEADER);
947
948                                let ip = log_ips.then(|| {
949                                    req.extensions()
950                                        .get::<ConnectInfo<SocketAddr>>()
951                                        .map(|addr| tracing::field::display(addr.ip()))
952                                });
953
954                                let query = req.uri().query().map(tracing::field::display);
955
956                                server_span_clone.in_scope(|| match request_id {
957                                    Some(rid) => {
958                                        tracing::info_span!(
959                                            "admin",
960                                            domain = %api_domain_clone,
961                                            rid = %rid
962                                                .to_str()
963                                                .unwrap_or(Uuid::new_v4().to_string().as_str()),
964                                            ip,
965                                            path = %req.uri().path(),
966                                            query,
967                                        )
968                                    }
969                                    None => {
970                                        tracing::info_span!(
971                                            "admin",
972                                            domain = %api_domain_clone,
973                                            rid = %Uuid::new_v4(),
974                                            ip,
975                                            path = %req.uri().path(),
976                                            query,
977                                        )
978                                    }
979                                })
980                            })
981                            .on_request(move |req: &Request<_>, _: &Span| {
982                                let hd = log_headers
983                                    .then_some(HeadersDebug(req.headers(), redacted_hash.clone()));
984
985                                #[cfg(tracing_unstable)]
986                                let headers = log_headers.then_some(tracing::field::valuable(&hd));
987
988                                #[cfg(not(tracing_unstable))]
989                                let headers = log_headers.then_some(tracing::field::debug(&hd));
990
991                                tracing::info!(
992                                    version = ?req.version(),
993                                    method = %req.method(),
994                                    headers,
995                                    "req"
996                                );
997                            })
998                            .on_response(move |res: &Response<_>, latency: Duration, _: &Span| {
999                                let hd = log_headers.then_some(HeadersDebug(
1000                                    res.headers(),
1001                                    redacted_hash_clone.clone(),
1002                                ));
1003
1004                                #[cfg(tracing_unstable)]
1005                                let headers = log_headers.then_some(tracing::field::valuable(&hd));
1006
1007                                #[cfg(not(tracing_unstable))]
1008                                let headers = log_headers.then_some(tracing::field::debug(&hd));
1009
1010                                let status = res.status().as_u16();
1011                                let latency = LatencyDisplay(latency.as_nanos() as f64);
1012
1013                                if status >= 500 {
1014                                    tracing::error!(status, headers, %latency, "res");
1015                                } else if status >= 400 {
1016                                    tracing::warn!(status, headers, %latency, "res");
1017                                } else {
1018                                    tracing::info!(status, headers, %latency, "res");
1019                                }
1020                            })
1021                            .on_failure(
1022                                |error: ServerErrorsFailureClass, _: Duration, _: &Span| {
1023                                    tracing::error!(
1024                                        err = %error,
1025                                        "fail"
1026                                    );
1027                                },
1028                            ),
1029                    )
1030                    .layer(TimeoutLayer::with_status_code(
1031                        StatusCode::REQUEST_TIMEOUT,
1032                        Duration::from_secs(10),
1033                    ))
1034                    .layer(PropagateRequestIdLayer::new(request_id))
1035                    .layer(SetResponseHeaderLayer::if_not_present(
1036                        header::SERVER,
1037                        HeaderValue::from_static("Ordinary"),
1038                    ))
1039                    .layer(SetResponseHeaderLayer::overriding(
1040                        header::ETAG,
1041                        modify_etag_for_encoding,
1042                    )),
1043            );
1044
1045        if state.secure {
1046            service = service.layer(SetResponseHeaderLayer::if_not_present(
1047                header::STRICT_TRANSPORT_SECURITY,
1048                HeaderValue::from_static("max-age=31536000"),
1049            ));
1050        }
1051
1052        if let Some(listener) = redirect_listener {
1053            let api_domain = self.config.domain.clone();
1054
1055            let request_id_header = HeaderName::from_static(REQUEST_ID_HEADER);
1056
1057            let local_addr = listener.local_addr()?;
1058            let state_clone = state.clone();
1059
1060            tokio::spawn(async move {
1061                use axum::extract::State;
1062
1063                let span = server_span_clone3;
1064
1065                span.in_scope(
1066                    || tracing::info!(server = %"redirect", addr = %local_addr, "listening"),
1067                );
1068
1069                let handler = async move |uri: Uri,
1070                                          headers: HeaderMap,
1071                                          State(state): State<Arc<OrdinaryApiServerState>>|
1072                            -> Result<Redirect, StatusCode> {
1073                    // todo: handle localhost redirecting
1074
1075                    if let Some(host) = get_host(&headers, &uri) {
1076                        if host == api_domain
1077                            && let Some(path_and_query) = uri.path_and_query()
1078                        {
1079                            return Ok(Redirect::permanent(&format!(
1080                                "https://{api_domain}{}",
1081                                path_and_query.as_str()
1082                            )));
1083                        }
1084
1085                        let apps = state.apps.read().await;
1086
1087                        if apps.contains_key(&host)
1088                            && let Some(path_and_query) = uri.path_and_query()
1089                        {
1090                            return Ok(Redirect::permanent(&format!(
1091                                "https://{host}{}",
1092                                path_and_query.as_str()
1093                            )));
1094                        }
1095                    }
1096
1097                    Err(StatusCode::NOT_FOUND)
1098                };
1099
1100                let service = redirect_service(
1101                    span,
1102                    redacted_hash_clone1,
1103                    log_ips,
1104                    log_headers,
1105                    request_id_header,
1106                    handler,
1107                    state_clone,
1108                );
1109
1110                if let Err(err) = axum::serve(
1111                    listener,
1112                    service.into_make_service_with_connect_info::<SocketAddr>(),
1113                )
1114                .with_graceful_shutdown(shutdown_signal())
1115                .await
1116                {
1117                    tracing::error!(%err, "server closed");
1118                }
1119            });
1120        }
1121
1122        let local_addr = listener.local_addr()?;
1123
1124        let span = server_span_clone2;
1125
1126        match mode {
1127            SecurityMode::Insecure => {
1128                span.in_scope(|| {
1129                    tracing::info!(
1130                        server = %"primary",
1131                        addr = %local_addr,
1132                        provision = %"none",
1133                        "listening"
1134                    );
1135                });
1136
1137                self.start_apps(&span, &state).await?;
1138
1139                axum::serve(
1140                    listener,
1141                    service.into_make_service_with_connect_info::<SocketAddr>(),
1142                )
1143                .with_graceful_shutdown(signal())
1144                .await?;
1145            }
1146            SecurityMode::Secure(cert_dir_path, provision) => {
1147                let span_clone = span.clone();
1148
1149                let (signal_tx, signal_rx) = watch::channel(());
1150                let (close_tx, close_rx) = watch::channel(());
1151
1152                {
1153                    let mut signal_tx_lock = state.signal_tx.write();
1154                    *signal_tx_lock = Some(signal_tx.clone());
1155
1156                    let mut close_rx_lock = state.close_rx.write();
1157                    *close_rx_lock = Some(close_rx.clone());
1158                }
1159
1160                let signal_tx_clone = state.signal_tx.clone();
1161
1162                tokio::spawn(async move {
1163                    signal().await;
1164
1165                    span_clone.in_scope(|| {
1166                        tracing::warn!("graceful shutdown");
1167                    });
1168
1169                    {
1170                        let mut signal_tx_lock = signal_tx_clone.write();
1171                        *signal_tx_lock = None;
1172                    }
1173
1174                    drop(signal_rx);
1175                });
1176
1177                match provision {
1178                    ProvisionMode::Localhost => {
1179                        let cert_dir_path = cert_dir_path.as_ref();
1180
1181                        ordinary_utils::generate_self_signed_localhost_certs(cert_dir_path)?;
1182
1183                        let rustls_config = rustls_server_config(
1184                            cert_dir_path.join("key.pem"),
1185                            cert_dir_path.join("crt.pem"),
1186                        )?;
1187
1188                        let tls_acceptor = TlsAcceptor::from(rustls_config);
1189                        span.in_scope(|| {
1190                            tracing::info!(
1191                                server = %"primary",
1192                                addr = %local_addr,
1193                                provision = %"localhost",
1194                                "listening"
1195                            );
1196                        });
1197
1198                        self.start_apps(&span, &state).await?;
1199
1200                        loop {
1201                            let span = span.clone();
1202                            let (cnx, addr) = tokio::select! {
1203                                conn = listener.accept() => conn?,
1204                                () = signal_tx.closed() => {
1205                                    span.in_scope(|| {
1206                                       tracing::warn!("not accepting new connections");
1207                                    });
1208                                    break;
1209                                }
1210                            };
1211
1212                            let tower_service = service.clone();
1213                            let tls_acceptor = tls_acceptor.clone();
1214
1215                            let signal_tx = signal_tx.clone();
1216                            let close_rx = close_rx.clone();
1217
1218                            tokio::spawn(async move {
1219                                // todo: determine a better timeout
1220                                match timeout(Duration::from_secs(3), tls_acceptor.accept(cnx))
1221                                    .await
1222                                {
1223                                    Ok(stream) => {
1224                                        let stream = match stream {
1225                                            Ok(stream) => TokioIo::new(stream),
1226                                            Err(err) => {
1227                                                span.in_scope(|| {
1228                                                    if log_ips {
1229                                                        tracing::warn!(ip = %addr.ip(), port = addr.port(), %err, "accept");
1230                                                    } else {
1231                                                        tracing::warn!(%err, "accept");
1232                                                    }
1233                                                });
1234
1235                                                return;
1236                                            }
1237                                        };
1238
1239                                        OrdinaryAppServer::handle_stream(
1240                                            &span,
1241                                            tower_service,
1242                                            addr,
1243                                            stream,
1244                                            &signal_tx,
1245                                            &close_rx,
1246                                            None,
1247                                        )
1248                                        .await;
1249                                    }
1250                                    Err(_) => {
1251                                        span.in_scope(|| {
1252                                            if log_ips {
1253                                                tracing::warn!(ip = %addr.ip(), port = addr.port(), "timeout");
1254                                            } else {
1255                                                tracing::warn!("timeout");
1256                                            }
1257                                        });
1258                                    }
1259                                }
1260                            });
1261                        }
1262                    }
1263                    ProvisionMode::Staging | ProvisionMode::Production => {
1264                        let contacts = self
1265                            .config
1266                            .contacts
1267                            .iter()
1268                            .map(|c| format!("mailto:{c}"))
1269                            .collect::<Vec<String>>();
1270
1271                        let cache_path = Path::new(cert_dir_path.as_ref()).join("cache");
1272
1273                        let acme_state = AcmeConfig::new([&self.config.domain])
1274                            .contact(contacts)
1275                            .cache_option(Some(DirCache::new(cache_path)))
1276                            .directory_lets_encrypt(provision == ProvisionMode::Production)
1277                            .state();
1278
1279                        let challenge_rustls_config = acme_state.challenge_rustls_config();
1280                        let mut default_rustls_config =
1281                            ServerConfig::builder_with_provider(Arc::new(ring::default_provider()))
1282                                .with_safe_default_protocol_versions()?
1283                                .with_no_client_auth()
1284                                .with_cert_resolver(acme_state.resolver());
1285
1286                        default_rustls_config.alpn_protocols =
1287                            vec![b"h2".to_vec(), b"http/1.1".to_vec()];
1288
1289                        let default_rustls_config = Arc::new(default_rustls_config);
1290
1291                        let api_domain_clone = self.config.domain.clone();
1292                        let span_clone = span.clone();
1293
1294                        let provision_str = match provision {
1295                            ProvisionMode::Production => "production",
1296                            ProvisionMode::Staging => "staging",
1297                            _ => unreachable!(),
1298                        };
1299
1300                        let acme_span = span_clone.in_scope(|| {
1301                            tracing::info_span!(
1302                                "acme",
1303                                domain = %api_domain_clone,
1304                                provision = %provision_str
1305                            )
1306                        });
1307
1308                        self.start_apps(&span, &state).await?;
1309                        ordinary_utils::acme_task(acme_span.clone(), acme_state, signal_tx.clone());
1310
1311                        span.in_scope(|| {
1312                            tracing::info!(
1313                                server = %"primary",
1314                                addr = %local_addr,
1315                                provision = %provision_str,
1316                                "listening"
1317                            );
1318                        });
1319
1320                        let api_domain = Arc::new(self.config.domain.clone());
1321
1322                        loop {
1323                            let span = span.clone();
1324                            let (cnx, addr) = tokio::select! {
1325                                conn = listener.accept() => conn?,
1326                                () = signal_tx.closed() => {
1327                                    span.in_scope(|| {
1328                                       tracing::warn!("not accepting new connections");
1329                                    });
1330                                    break;
1331                                }
1332                            };
1333
1334                            let tower_service = service.clone();
1335                            let acme_span = acme_span.clone();
1336
1337                            let challenge_rustls_config = challenge_rustls_config.clone();
1338                            let default_rustls_config = default_rustls_config.clone();
1339
1340                            let api_domain = api_domain.clone();
1341                            let apps = self.apps.clone();
1342
1343                            let signal_tx = signal_tx.clone();
1344                            let close_rx = close_rx.clone();
1345
1346                            tokio::spawn(async move {
1347                                // todo: determine a better timeout
1348                                match timeout(
1349                                    Duration::from_secs(3),
1350                                    LazyConfigAcceptor::new(Acceptor::default(), cnx),
1351                                )
1352                                .await
1353                                {
1354                                    Ok(start_handshake) => {
1355                                        let start_handshake = match start_handshake {
1356                                            Ok(start_handshake) => start_handshake,
1357                                            Err(err) => {
1358                                                if log_ips {
1359                                                    let tls_span = span.in_scope(|| { tracing::error_span!("tls", ip = %addr.ip(), port = addr.port()) });
1360
1361                                                    tls_span.in_scope(
1362                                                        || tracing::error!(%err, "accept"),
1363                                                    );
1364                                                } else {
1365                                                    let tls_span = span
1366                                                        .in_scope(|| tracing::error_span!("tls"));
1367
1368                                                    tls_span.in_scope(
1369                                                        || tracing::error!(%err, "accept"),
1370                                                    );
1371                                                }
1372
1373                                                return;
1374                                            }
1375                                        };
1376
1377                                        if let Some(sni) =
1378                                            start_handshake.client_hello().server_name()
1379                                        {
1380                                            let tls_span = span.in_scope(|| {
1381                                                if log_ips {
1382                                                    tracing::info_span!("tls", %sni, ip = %addr.ip(), port = addr.port())
1383                                                } else {
1384                                                    tracing::info_span!("tls", %sni)
1385                                                }
1386                                            });
1387
1388                                            if sni == *api_domain {
1389                                                if let Err(err) =
1390                                                    OrdinaryAppServer::handle_handshake(
1391                                                        &tls_span,
1392                                                        &acme_span,
1393                                                        start_handshake,
1394                                                        tower_service,
1395                                                        addr,
1396                                                        challenge_rustls_config,
1397                                                        default_rustls_config,
1398                                                        &signal_tx,
1399                                                        &close_rx,
1400                                                        None,
1401                                                    )
1402                                                    .await
1403                                                {
1404                                                    tls_span.in_scope(|| {
1405                                                        tracing::error!(%err, "handshake");
1406                                                    });
1407                                                }
1408                                            } else {
1409                                                let apps = apps.read().await;
1410
1411                                                if let Some(app) = apps.get(sni) {
1412                                                    // todo: WrappedApp.live: Arc<RwLock<bool>>
1413                                                    // todo: just handle the stream here
1414                                                    if let Err(err) = app.stream_tx.send((
1415                                                        start_handshake,
1416                                                        addr,
1417                                                        tls_span.clone(),
1418                                                    )) {
1419                                                        tls_span.in_scope(
1420                                                            || tracing::error!(%err, "handoff"),
1421                                                        );
1422                                                    }
1423                                                } else {
1424                                                    tls_span
1425                                                        .in_scope(|| tracing::warn!("no match"));
1426                                                }
1427                                            }
1428                                        } else {
1429                                            let tls_span = span.in_scope(|| {
1430                                                if log_ips {
1431                                                    tracing::info_span!("tls", ip = %addr.ip(), port = addr.port())
1432                                                } else {
1433                                                    tracing::info_span!("tls")
1434                                                }
1435                                            });
1436
1437                                            tls_span.in_scope(|| tracing::warn!("no sni"));
1438                                        }
1439                                    }
1440                                    Err(_) => {
1441                                        if log_ips {
1442                                            let tls_span = span.in_scope(|| { tracing::error_span!("tls", ip = %addr.ip(), port = addr.port()) });
1443
1444                                            tls_span.in_scope(|| tracing::error!("timeout"));
1445                                        } else {
1446                                            let tls_span =
1447                                                span.in_scope(|| tracing::error_span!("tls"));
1448
1449                                            tls_span.in_scope(|| tracing::error!("timeout"));
1450                                        }
1451                                    }
1452                                }
1453                            });
1454                        }
1455                    }
1456                }
1457
1458                {
1459                    let mut close_rx_lock = state.close_rx.write();
1460                    *close_rx_lock = None;
1461                }
1462
1463                drop(close_rx);
1464                drop(listener);
1465
1466                span.in_scope(|| {
1467                    tracing::warn!(
1468                        "waiting for {} task(s) to finish",
1469                        close_tx.receiver_count()
1470                    );
1471                });
1472                close_tx.closed().await;
1473            }
1474        }
1475
1476        Ok(())
1477    }
1478
1479    #[allow(clippy::too_many_lines)]
1480    async fn start_apps(
1481        &self,
1482        server_span: &Span,
1483        state: &Arc<OrdinaryApiServerState>,
1484    ) -> anyhow::Result<()> {
1485        let state_clone = state.clone();
1486
1487        let Ok(shared_rt) = tokio::runtime::Handle::try_current() else {
1488            bail!("failed to get shared runtime");
1489        };
1490
1491        Box::pin(async {
1492            // todo: sleep this for 2 seconds or something to wait for the server to get started up
1493
1494            let mut apps = state_clone.apps.write().await;
1495
1496            for entry in fs::read_dir(&self.apps_dir)?.flatten() {
1497                let config_path = entry.path().join("ordinary.json");
1498                let killed_path = entry.path().join("killed");
1499
1500                match fs::read(&config_path) {
1501                    Ok(mut config_bytes) => match simd_json::from_slice::<OrdinaryConfig>(&mut config_bytes[..]) {
1502                        Ok(config) => {
1503                            let domain = config.domain.clone();
1504
1505                            if state.stored_logs
1506                                && let Some(monitor) = &*state.monitor
1507                                && let Err(err) = monitor
1508                                .add(
1509                                    config.domain.as_str(),
1510                                    &config.cnames.clone().unwrap_or_default(),
1511                                )
1512                            {
1513                                tracing::error!(%err, "failed to add domain to log manager");
1514                            }
1515
1516                            let app_span = Some(state.server_span.clone().in_scope(|| {
1517                                tracing::info_span!("app", domain = %domain)
1518                            }));
1519
1520                            match ops::app::dbs(&state_clone, &config, &entry.path(), app_span.clone()).await {
1521                                Ok((auth, storage)) => {
1522                                    match ops::app::start(
1523                                        &state_clone,
1524                                        &config,
1525                                        auth,
1526                                        storage,
1527                                        match state_clone
1528                                            .provision_mode
1529                                            .as_ref()
1530                                            .unwrap_or(&ProvisionMode::Localhost)
1531                                        {
1532                                            ProvisionMode::Localhost => {
1533                                                ordinary_app::server::ProvisionMode::Localhost
1534                                            }
1535                                            ProvisionMode::Staging => {
1536                                                ordinary_app::server::ProvisionMode::Staging
1537                                            }
1538                                            ProvisionMode::Production => {
1539                                                ordinary_app::server::ProvisionMode::Production
1540                                            }
1541                                        },
1542                                        entry.path().join("certs"),
1543                                        shared_rt.clone(),
1544                                        app_span.clone(),
1545                                    ).await {
1546                                        Ok((port, app, terminate_tx, stream_tx)) => {
1547                                            let cnames = app.config.cnames.clone();
1548
1549                                            let static_secret = StaticSecret::random_from_rng(OsRng);
1550                                            let public_key = PublicKey::from(&static_secret);
1551
1552                                            let app = Arc::new(WrappedOrdinaryAppServer {
1553                                                port,
1554                                                app,
1555                                                terminate_tx: terminate_tx.clone(),
1556                                                stream_tx,
1557                                                dh_keypair: (static_secret, public_key),
1558                                            });
1559
1560                                            if let Some(cnames) = cnames {
1561                                                for cname in cnames {
1562                                                    apps.insert(cname, app.clone());
1563                                                }
1564                                            }
1565
1566                                            apps.insert(
1567                                                domain,
1568                                                app,
1569                                            );
1570
1571                                            if killed_path.exists() && let Some(app_span) = app_span {
1572                                                async {
1573                                                    if let Ok(since) = tokio::fs::read_to_string(killed_path).await {
1574                                                        tracing::info!(%since, "killed");
1575                                                    }
1576                                                }.instrument(app_span).await;
1577                                            }
1578                                        }
1579                                        Err(err) => {
1580                                            if let Some(app_span) = app_span {
1581                                                app_span.in_scope(|| {
1582                                                    tracing::error!(%err, "failed to start app");
1583                                                });
1584                                            }
1585                                        }
1586                                    }
1587                                }
1588                                Err(err) => {
1589                                    if let Some(app_span) = app_span {
1590                                        app_span.in_scope(|| {
1591                                            tracing::error!(%err, "failed to init dbs");
1592                                        });
1593                                    }
1594                                }
1595                            }
1596                        }
1597                        Err(err) => {
1598                            if let Some(path_str) = config_path.to_str() {
1599                                tracing::error!(%err, path = path_str, "failed to parse config file");
1600                            } else {
1601                                tracing::error!(%err, "failed to parse config file");
1602                            }
1603                        }
1604                    },
1605                    Err(err) => {
1606                        if !entry.path().join("erased").exists() {
1607                            if let Some(path_str) = config_path.to_str() {
1608                                tracing::warn!(%err, path = path_str, "failed to read config file");
1609                            } else {
1610                                tracing::warn!(%err, "failed to read config file");
1611                            }
1612                        }
1613                    }
1614                }
1615            }
1616
1617            drop(apps);
1618
1619            anyhow::Ok(())
1620
1621        }.instrument(server_span.clone())).await
1622    }
1623}