manta_server/server/
mod.rs1pub mod api_doc;
5pub mod auth_middleware;
6pub mod common;
7pub mod handlers;
8pub mod routes;
9
10use std::collections::HashMap;
11use std::net::SocketAddr;
12use std::sync::Arc;
13
14use axum_server::tls_rustls::RustlsConfig;
15use manta_backend_dispatcher::error::Error;
16use std::time::Duration;
17
18use crate::manta_backend_dispatcher::StaticBackendDispatcher;
19use crate::server::common::app_context::InfraContext;
20use manta_shared::common::kafka::Kafka;
21
22pub struct SiteBackend {
26 pub backend: StaticBackendDispatcher,
28 pub shasta_base_url: String,
30 pub shasta_root_cert: Vec<u8>,
32 pub socks5_proxy: Option<String>,
34 pub vault_base_url: Option<String>,
36 pub gitea_base_url: String,
38 pub k8s_api_url: Option<String>,
40}
41
42pub struct ServerState {
49 pub sites: HashMap<String, SiteBackend>,
51 pub console_inactivity_timeout: Duration,
54 pub auditor: Option<Kafka>,
57 pub auth_rate_limit_per_minute: Option<u32>,
60}
61
62impl ServerState {
63 pub fn infra_context<'a>(
69 &'a self,
70 site_name: &'a str,
71 ) -> Result<InfraContext<'a>, Error> {
72 let site = self.sites.get(site_name).ok_or_else(|| {
73 Error::NotFound(format!("site '{site_name}' not found"))
74 })?;
75 Ok(InfraContext {
76 backend: &site.backend,
77 site_name,
78 shasta_base_url: &site.shasta_base_url,
79 shasta_root_cert: &site.shasta_root_cert,
80 socks5_proxy: site.socks5_proxy.as_deref(),
81 vault_base_url: site.vault_base_url.as_deref(),
82 gitea_base_url: &site.gitea_base_url,
83 k8s_api_url: site.k8s_api_url.as_deref(),
84 })
85 }
86}
87
88async fn log_requests(
89 request: axum::extract::Request,
90 next: axum::middleware::Next,
91) -> axum::response::Response {
92 let method = request.method().clone();
93 let uri = request.uri().clone();
94 let response = next.run(request).await;
95 tracing::info!("{} {} → {}", method, uri, response.status());
96 response
97}
98
99pub async fn start_server(
104 state: Arc<ServerState>,
105 listen_addr: &str,
106 port: u16,
107 cert_path: Option<&str>,
108 key_path: Option<&str>,
109) -> Result<(), Error> {
110 let app = routes::build_router(state)
111 .layer(tower_http::timeout::TimeoutLayer::with_status_code(
112 axum::http::StatusCode::REQUEST_TIMEOUT,
113 Duration::from_secs(60),
114 ))
115 .layer(axum::middleware::from_fn(log_requests));
116
117 let addr: SocketAddr = format!("{listen_addr}:{port}")
118 .parse()
119 .map_err(|e| Error::BadRequest(format!("Invalid listen address: {e}")))?;
120
121 match (cert_path, key_path) {
122 (Some(cert), Some(key)) => {
123 let tls_config = RustlsConfig::from_pem_file(cert, key).await?;
124 let handle = axum_server::Handle::new();
125 let ready_handle = handle.clone();
126 tokio::spawn(async move {
127 ready_handle.listening().await;
128 tracing::info!(
129 "HTTPS server ready, accepting requests on https://{}",
130 addr
131 );
132 eprintln!("HTTPS server ready, accepting requests on https://{addr}");
133 });
134 axum_server::bind_rustls(addr, tls_config)
135 .handle(handle)
136 .serve(app.into_make_service_with_connect_info::<SocketAddr>())
137 .await?;
138 }
139 (None, None) => {
140 let handle = axum_server::Handle::new();
141 let ready_handle = handle.clone();
142 tokio::spawn(async move {
143 ready_handle.listening().await;
144 tracing::info!(
145 "HTTP server ready, accepting requests on http://{}",
146 addr
147 );
148 eprintln!("HTTP server ready, accepting requests on http://{addr}");
149 });
150 axum_server::bind(addr)
151 .handle(handle)
152 .serve(app.into_make_service_with_connect_info::<SocketAddr>())
153 .await?;
154 }
155 _ => {
156 return Err(Error::BadRequest(
157 "--cert and --key must be provided together".to_string(),
158 ));
159 }
160 }
161
162 Ok(())
163}