mirror of
https://github.com/michaelshimeles/terminal.git
synced 2026-11-07 05:56:17 -10:00
* Gate /stats and /metrics behind an optional token and harden the gateway - TR_METRICS_TOKEN: when set, /stats and /metrics answer 404 unless the request sends it as a bearer token. /healthz and /readyz stay public. The token never shows in Debug output or errors. - TR_MAX_CLIENTS_PER_IP caps connections per address (IPv6 per /64), off by default. TR_CLIENT_IP_HEADER takes the address from the edge's header. The first connection logs whether x-forwarded-for was there, never its value, so the cap can be checked behind Freestyle's edge. - A client that keeps sending after a budget's worth of RateLimited replies is closed with 1008 instead of being answered forever. - cli stats sends TR_METRICS_TOKEN when set. - vm-setup.sh: optional /etc/tr-gateway/env, stop timeout, memory and task caps, and systemd sandboxing (security score 8.3 to 3.1). * Let the site's server URL come from a repository variable deploy-site.yml passes the TR_SERVER_URL repository variable to build-web.sh when it's set, so moving the site to the gateway's custom domain is a variable and a redeploy, not a code change. * Accept the bearer scheme in any case HTTP auth scheme names are case-insensitive, so "bearer <token>" now works too. The token itself is still compared exactly.
98 lines
3.0 KiB
Rust
98 lines
3.0 KiB
Rust
//! `tr-gateway`, the Terminal data server (`docs/architecture/server.md`).
|
|
//!
|
|
//! For now it carries Hyperliquid's public feed ([`hl::HlPublicProvider`]): one shared set of
|
|
//! upstream connections, fanned out to every client over the protocol in `tr-proto`.
|
|
|
|
pub mod access;
|
|
pub mod config;
|
|
pub mod hl;
|
|
pub mod http;
|
|
pub mod limit;
|
|
pub mod metrics;
|
|
pub mod provider;
|
|
pub mod session;
|
|
pub mod store;
|
|
|
|
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
|
|
use anyhow::{Context, Result};
|
|
use tokio::sync::{oneshot, watch};
|
|
use tokio::task::JoinHandle;
|
|
use tr_core::Bar;
|
|
|
|
pub use config::Config;
|
|
|
|
use crate::hl::HlPublicProvider;
|
|
use crate::metrics::Metrics;
|
|
use crate::store::Hub;
|
|
|
|
/// Keep at most `limit` bars opening before `end`.
|
|
pub fn clip(mut bars: Vec<Bar>, end: Option<i64>, limit: usize) -> Vec<Bar> {
|
|
if let Some(end) = end {
|
|
bars.retain(|b| b.ts < end);
|
|
}
|
|
if bars.len() > limit {
|
|
bars.drain(..bars.len() - limit);
|
|
}
|
|
bars
|
|
}
|
|
|
|
/// A running gateway.
|
|
pub struct Gateway {
|
|
pub addr: SocketAddr,
|
|
pub state: http::AppState,
|
|
stop: Option<oneshot::Sender<()>>,
|
|
closing: watch::Sender<bool>,
|
|
task: JoinHandle<()>,
|
|
}
|
|
|
|
impl Gateway {
|
|
pub fn hub(&self) -> &Arc<Hub> {
|
|
&self.state.hub
|
|
}
|
|
|
|
/// Stop accepting, close every client session and upstream connection, and wait for the
|
|
/// server to finish. Nothing keeps running afterwards.
|
|
pub async fn shutdown(mut self) {
|
|
self.closing.send_replace(true);
|
|
if let Some(s) = self.stop.take() {
|
|
let _ = s.send(());
|
|
}
|
|
self.state.hub.stop();
|
|
let _ = (&mut self.task).await;
|
|
}
|
|
|
|
/// Run until the process is asked to stop.
|
|
pub async fn wait(self) {
|
|
let _ = self.task.await;
|
|
}
|
|
}
|
|
|
|
/// Bind, connect upstream and serve in the background.
|
|
pub async fn start(cfg: Config) -> Result<Gateway> {
|
|
let metrics = Arc::new(Metrics::default());
|
|
let provider = Arc::new(HlPublicProvider::new(cfg.hl.clone(), metrics.clone())?);
|
|
let hub = Hub::new(provider, cfg.hub.clone(), metrics);
|
|
hub.start();
|
|
let (closing, closing_rx) = watch::channel(false);
|
|
let access = http::Access::new(cfg.metrics_token.clone(), cfg.max_clients_per_ip, cfg.client_ip_header.clone());
|
|
let state = http::AppState::new(hub, cfg.session.clone(), cfg.max_clients, closing_rx).with_access(access);
|
|
let listener = tokio::net::TcpListener::bind(cfg.bind).await.context("binding the listen address")?;
|
|
let addr = listener.local_addr()?;
|
|
let (stop, stopped) = oneshot::channel::<()>();
|
|
let app = http::router(state.clone());
|
|
let task = tokio::spawn(async move {
|
|
let shutdown = async {
|
|
let _ = stopped.await;
|
|
};
|
|
if let Err(e) = axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
|
|
.with_graceful_shutdown(shutdown)
|
|
.await
|
|
{
|
|
tracing::error!("server: {e}");
|
|
}
|
|
});
|
|
Ok(Gateway { addr, state, stop: Some(stop), closing, task })
|
|
}
|