1mod 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[](https://docs.rs/ordinary-api/0.6.0-pre.13)
105[](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
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
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 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, ®_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 ®_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 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 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 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 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 .route("/healthz", get(|| async { StatusCode::OK }))
817 .route("/v1/keys/dh/public", get(ops::keys::dh_public_key))
819 .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 .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 .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 .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 .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 .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 .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 .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 .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 .route("/v1/app/accounts/invite", get(ops::app::invite))
910 .route("/v1/app/accounts", get(ops::app::accounts_list))
911 .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 .route("/v1/templates", put(ops::templates::upload))
922 .route("/v1/content", put(ops::content::update))
924 .route("/v1/secrets", put(ops::secrets::store))
926 .route("/v1/assets", put(ops::assets::write))
928 .route("/v1/actions", put(ops::actions::install))
930 .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 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 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 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 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 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}