1mod 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[](https://docs.rs/ordinary-api/0.7.0)
105[](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
263pub(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 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 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, ®_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 ®_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 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 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 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 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 .route("/healthz", get(|| async { StatusCode::OK }))
836 .route("/v1/keys/dh/public", get(ops::keys::dh_public_key))
838 .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 .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 .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 .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 .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 .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 .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 .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 .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 .route("/v1/app/accounts/invite", get(ops::app::invite))
929 .route("/v1/app/accounts", get(ops::app::accounts_list))
930 .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 .route("/v1/templates", put(ops::templates::upload))
941 .route("/v1/content", put(ops::content::update))
943 .route("/v1/secrets", put(ops::secrets::store))
945 .route("/v1/assets", put(ops::assets::write))
947 .route("/v1/actions", put(ops::actions::install))
949 .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 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 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 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 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}