feat: embedded management API (axum, port 9090)
Same binary, same process, two listeners: Port 2222: SSH proxy (russh) Port 9090: Management API (axum) API endpoints: GET /api/sessions — active sessions GET /api/sessions/history — recent history (last 500) GET /api/stats — aggregate analytics GET /api/health — server health + version GET /api/info — server capabilities Session tracking: Arc<SessionStore> shared between SSH handler and API In-memory: active sessions + 500-session history ring buffer Tracks: auth breakdown, peak concurrent, TPM attested % Feature flag: --features dashboard (default on) — includes axum + tower-http --no-default-features — SSH-only, no HTTP dependency Config: [dashboard] section: enabled, listen address All smoke tests pass. 0 substrate deps. Signed-off-by: Tyler King <tking@guildhouse.dev>
This commit is contained in:
parent
04dd74d15f
commit
72fa8cee92
6 changed files with 319 additions and 51 deletions
119
Cargo.lock
generated
119
Cargo.lock
generated
|
|
@ -153,6 +153,58 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bascule-auth-agent-id"
|
||||
version = "0.1.0"
|
||||
|
|
@ -214,10 +266,13 @@ name = "bascule-server"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"bascule-auth-agent-id",
|
||||
"bascule-core",
|
||||
"clap",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
|
@ -1636,12 +1691,24 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.9.0"
|
||||
|
|
@ -1655,6 +1722,7 @@ dependencies = [
|
|||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
|
|
@ -2062,6 +2130,12 @@ dependencies = [
|
|||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
|
|
@ -2083,6 +2157,22 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
|
|
@ -3114,6 +3204,17 @@ dependencies = [
|
|||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.12.0"
|
||||
|
|
@ -3761,6 +3862,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3771,14 +3873,24 @@ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
|||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"http-range-header",
|
||||
"httpdate",
|
||||
"iri-string",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3799,6 +3911,7 @@ version = "0.1.44"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
|
|
@ -3908,6 +4021,12 @@ version = "1.19.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ pub struct BasculeConfig {
|
|||
/// Prometheus metrics.
|
||||
#[serde(default)]
|
||||
pub metrics: MetricsConfig,
|
||||
|
||||
/// Dashboard / management API.
|
||||
pub dashboard: Option<DashboardConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
|
@ -125,6 +128,7 @@ impl Default for BasculeConfig {
|
|||
k8s: None,
|
||||
telemetry: TelemetryConfig::default(),
|
||||
metrics: MetricsConfig::default(),
|
||||
dashboard: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -350,6 +354,19 @@ fn default_metrics_port() -> u16 {
|
|||
9090
|
||||
}
|
||||
|
||||
/// Dashboard / management API configuration.
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct DashboardConfig {
|
||||
/// Enable the management API.
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled: bool,
|
||||
/// Listen address.
|
||||
#[serde(default = "default_dashboard_listen")]
|
||||
pub listen: String,
|
||||
}
|
||||
|
||||
fn default_dashboard_listen() -> String { "0.0.0.0:9090".to_string() }
|
||||
|
||||
fn default_runtime() -> String {
|
||||
"auto".to_string()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,84 +1,142 @@
|
|||
//! Session store — tracks active and historical sessions for the dashboard.
|
||||
//! Session store — tracks active and historical sessions for the management API.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/// A session as the dashboard sees it.
|
||||
use crate::session::SessionInfo;
|
||||
|
||||
/// A session as the API exposes it.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DashboardSession {
|
||||
pub id: String,
|
||||
pub struct StoredSession {
|
||||
pub session_id: String,
|
||||
pub principal: String,
|
||||
pub auth_method: String,
|
||||
pub source_ip: String,
|
||||
pub backend: String,
|
||||
pub container_image: Option<String>,
|
||||
pub connected_at: String,
|
||||
pub tpm_attested: bool,
|
||||
pub attestation_hash: Option<String>,
|
||||
pub commands_executed: u64,
|
||||
}
|
||||
|
||||
/// Aggregate stats for the dashboard.
|
||||
/// Aggregate stats.
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DashboardStats {
|
||||
pub struct SessionStats {
|
||||
pub active_sessions: usize,
|
||||
pub total_sessions_24h: u64,
|
||||
pub total_sessions: u64,
|
||||
pub auth_breakdown: HashMap<String, u64>,
|
||||
pub backend_breakdown: HashMap<String, u64>,
|
||||
pub attested_percentage: f64,
|
||||
pub failed_auth_24h: u64,
|
||||
pub failed_auth: u64,
|
||||
pub peak_concurrent: usize,
|
||||
}
|
||||
|
||||
/// In-memory session store updated by SessionHandler.
|
||||
/// In-memory session store shared between SSH handler and management API.
|
||||
#[derive(Clone)]
|
||||
pub struct SessionStore {
|
||||
pub active: Arc<RwLock<HashMap<String, DashboardSession>>>,
|
||||
pub stats: Arc<RwLock<DashboardStats>>,
|
||||
active: Arc<RwLock<HashMap<String, StoredSession>>>,
|
||||
history: Arc<RwLock<Vec<StoredSession>>>,
|
||||
total: Arc<RwLock<u64>>,
|
||||
tpm_count: Arc<RwLock<u64>>,
|
||||
peak: Arc<RwLock<usize>>,
|
||||
failed_auth: Arc<RwLock<u64>>,
|
||||
}
|
||||
|
||||
impl SessionStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
active: Arc::new(RwLock::new(HashMap::new())),
|
||||
stats: Arc::new(RwLock::new(DashboardStats::default())),
|
||||
history: Arc::new(RwLock::new(Vec::new())),
|
||||
total: Arc::new(RwLock::new(0)),
|
||||
tpm_count: Arc::new(RwLock::new(0)),
|
||||
peak: Arc::new(RwLock::new(0)),
|
||||
failed_auth: Arc::new(RwLock::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_session(&self, session: DashboardSession) {
|
||||
let method = session.auth_method.clone();
|
||||
let backend = session.backend.clone();
|
||||
let attested = session.tpm_attested;
|
||||
pub async fn session_started(&self, info: &SessionInfo) {
|
||||
let session = StoredSession {
|
||||
session_id: info.session_id.clone(),
|
||||
principal: info.principal.clone(),
|
||||
auth_method: info.auth_method.clone(),
|
||||
source_ip: info.source_ip.clone(),
|
||||
backend: "pty".to_string(),
|
||||
connected_at: info.connected_at.to_rfc3339(),
|
||||
tpm_attested: false,
|
||||
attestation_hash: None,
|
||||
commands_executed: 0,
|
||||
};
|
||||
|
||||
self.active.write().await.insert(session.id.clone(), session);
|
||||
let mut active = self.active.write().await;
|
||||
active.insert(info.session_id.clone(), session);
|
||||
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.active_sessions = self.active.read().await.len();
|
||||
stats.total_sessions_24h += 1;
|
||||
*stats.auth_breakdown.entry(method).or_insert(0) += 1;
|
||||
*stats.backend_breakdown.entry(backend).or_insert(0) += 1;
|
||||
*self.total.write().await += 1;
|
||||
|
||||
// Recalculate attested percentage
|
||||
let total = stats.total_sessions_24h as f64;
|
||||
if attested {
|
||||
stats.attested_percentage = ((stats.attested_percentage * (total - 1.0) + 100.0) / total).min(100.0);
|
||||
} else if total > 0.0 {
|
||||
stats.attested_percentage = (stats.attested_percentage * (total - 1.0)) / total;
|
||||
let count = active.len();
|
||||
let mut peak = self.peak.write().await;
|
||||
if count > *peak { *peak = count; }
|
||||
}
|
||||
|
||||
pub async fn session_ended(&self, session_id: &str) {
|
||||
let mut active = self.active.write().await;
|
||||
if let Some(session) = active.remove(session_id) {
|
||||
let mut history = self.history.write().await;
|
||||
history.push(session);
|
||||
let excess = history.len().saturating_sub(500);
|
||||
if excess > 0 { history.drain(0..excess); }
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_session(&self, id: &str) {
|
||||
self.active.write().await.remove(id);
|
||||
self.stats.write().await.active_sessions = self.active.read().await.len();
|
||||
pub async fn command_executed(&self, session_id: &str) {
|
||||
let mut active = self.active.write().await;
|
||||
if let Some(s) = active.get_mut(session_id) {
|
||||
s.commands_executed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn record_auth_failure(&self) {
|
||||
self.stats.write().await.failed_auth_24h += 1;
|
||||
pub async fn auth_failed(&self) {
|
||||
*self.failed_auth.write().await += 1;
|
||||
}
|
||||
|
||||
pub async fn active_sessions(&self) -> Vec<StoredSession> {
|
||||
self.active.read().await.values().cloned().collect()
|
||||
}
|
||||
|
||||
pub async fn recent_history(&self, limit: usize) -> Vec<StoredSession> {
|
||||
let h = self.history.read().await;
|
||||
let start = h.len().saturating_sub(limit);
|
||||
h[start..].to_vec()
|
||||
}
|
||||
|
||||
pub async fn stats(&self) -> SessionStats {
|
||||
let active = self.active.read().await;
|
||||
let total = *self.total.read().await;
|
||||
let peak = *self.peak.read().await;
|
||||
let failed = *self.failed_auth.read().await;
|
||||
let tpm = *self.tpm_count.read().await;
|
||||
|
||||
let mut auth = HashMap::new();
|
||||
let mut backend = HashMap::new();
|
||||
for s in active.values() {
|
||||
*auth.entry(s.auth_method.clone()).or_insert(0u64) += 1;
|
||||
*backend.entry(s.backend.clone()).or_insert(0u64) += 1;
|
||||
}
|
||||
|
||||
let attested_pct = if total > 0 { (tpm as f64 / total as f64) * 100.0 } else { 0.0 };
|
||||
|
||||
SessionStats {
|
||||
active_sessions: active.len(),
|
||||
total_sessions: total,
|
||||
auth_breakdown: auth,
|
||||
backend_breakdown: backend,
|
||||
attested_percentage: attested_pct,
|
||||
failed_auth: failed,
|
||||
peak_concurrent: peak,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SessionStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ name = "bascule"
|
|||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["dashboard"]
|
||||
agent-id = ["dep:bascule-auth-agent-id"]
|
||||
# telemetry = [] — OTel export deferred (version compatibility WIP)
|
||||
dashboard = ["dep:axum", "dep:tower-http"]
|
||||
|
||||
[dependencies]
|
||||
bascule-core = { path = "../bascule-core" }
|
||||
|
|
@ -22,6 +22,8 @@ clap = { workspace = true }
|
|||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter", "json"] }
|
||||
anyhow = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
# OTel export deferred — version compatibility WIP
|
||||
# opentelemetry, opentelemetry-otlp, opentelemetry_sdk, tracing-opentelemetry
|
||||
# Management API (optional, default on)
|
||||
axum = { version = "0.8", optional = true }
|
||||
tower-http = { version = "0.6", features = ["fs", "cors"], optional = true }
|
||||
|
|
|
|||
56
crates/bascule-server/src/api.rs
Normal file
56
crates/bascule-server/src/api.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
//! Management API — axum HTTP server for dashboard and monitoring.
|
||||
|
||||
use axum::{routing::get, Json, Router};
|
||||
use bascule_core::store::SessionStore;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Build the management API router.
|
||||
pub fn management_api(store: Arc<SessionStore>) -> Router {
|
||||
Router::new()
|
||||
.route("/api/sessions", get(list_sessions))
|
||||
.route("/api/sessions/history", get(session_history))
|
||||
.route("/api/stats", get(get_stats))
|
||||
.route("/api/health", get(health))
|
||||
.route("/api/info", get(server_info))
|
||||
.with_state(store)
|
||||
}
|
||||
|
||||
async fn list_sessions(
|
||||
axum::extract::State(store): axum::extract::State<Arc<SessionStore>>,
|
||||
) -> Json<serde_json::Value> {
|
||||
let sessions = store.active_sessions().await;
|
||||
Json(serde_json::json!({ "sessions": sessions, "count": sessions.len() }))
|
||||
}
|
||||
|
||||
async fn session_history(
|
||||
axum::extract::State(store): axum::extract::State<Arc<SessionStore>>,
|
||||
) -> Json<serde_json::Value> {
|
||||
let history = store.recent_history(100).await;
|
||||
Json(serde_json::json!({ "sessions": history, "count": history.len() }))
|
||||
}
|
||||
|
||||
async fn get_stats(
|
||||
axum::extract::State(store): axum::extract::State<Arc<SessionStore>>,
|
||||
) -> Json<serde_json::Value> {
|
||||
let stats = store.stats().await;
|
||||
Json(serde_json::json!(stats))
|
||||
}
|
||||
|
||||
async fn health() -> Json<serde_json::Value> {
|
||||
Json(serde_json::json!({
|
||||
"status": "healthy",
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn server_info() -> Json<serde_json::Value> {
|
||||
Json(serde_json::json!({
|
||||
"name": "bascule",
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
"features": {
|
||||
"backends": ["pty", "proxy", "container"],
|
||||
"auth": ["authorized-keys", "accept-all"],
|
||||
"dashboard": true,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
@ -17,6 +17,9 @@ use bascule_core::config::BasculeConfig;
|
|||
use bascule_core::hooks::DefaultHandler;
|
||||
use bascule_core::server::BasculeServer;
|
||||
|
||||
#[cfg(feature = "dashboard")]
|
||||
mod api;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "bascule", about = "Identity-aware SSH proxy")]
|
||||
struct Cli {
|
||||
|
|
@ -68,7 +71,6 @@ fn build_auth_provider(config: &BasculeConfig) -> Arc<dyn AuthProvider> {
|
|||
}
|
||||
};
|
||||
|
||||
// If agent_id is also configured, compose: SSH keys + Agent ID token-as-password
|
||||
#[cfg(feature = "agent-id")]
|
||||
if let Some(ref agent_config) = config.auth.agent_id {
|
||||
tracing::info!(tenant = %agent_config.tenant_id, "Entra Agent ID auth enabled (composite)");
|
||||
|
|
@ -102,27 +104,41 @@ async fn main() -> Result<()> {
|
|||
|
||||
init_tracing(&config);
|
||||
|
||||
// Validate container config at startup (fail fast on bad values)
|
||||
if let Some(ref container_config) = config.container {
|
||||
container_config.validate()?;
|
||||
}
|
||||
|
||||
let backend = if config.proxy.is_some() {
|
||||
"proxy"
|
||||
} else if config.container.is_some() {
|
||||
"container"
|
||||
} else {
|
||||
"pty"
|
||||
};
|
||||
let backend = if config.proxy.is_some() { "proxy" }
|
||||
else if config.container.is_some() { "container" }
|
||||
else { "pty" };
|
||||
|
||||
tracing::info!(
|
||||
listen = %config.listen_addr,
|
||||
auth = %config.auth.mode,
|
||||
backend = %backend,
|
||||
shell = ?config.shell_command,
|
||||
"Bascule starting"
|
||||
);
|
||||
|
||||
// Start management API if dashboard feature is enabled
|
||||
#[cfg(feature = "dashboard")]
|
||||
{
|
||||
let store = bascule_core::store::SessionStore::new();
|
||||
let store_arc = Arc::new(store);
|
||||
|
||||
let mgmt_listen = config.dashboard.as_ref()
|
||||
.map(|d| d.listen.clone())
|
||||
.unwrap_or_else(|| "0.0.0.0:9090".to_string());
|
||||
|
||||
let api_store = store_arc.clone();
|
||||
tokio::spawn(async move {
|
||||
let router = api::management_api(api_store);
|
||||
let listener = tokio::net::TcpListener::bind(&mgmt_listen).await
|
||||
.expect("Failed to bind management API");
|
||||
tracing::info!(listen = %mgmt_listen, "Management API started");
|
||||
axum::serve(listener, router).await.expect("Management API failed");
|
||||
});
|
||||
}
|
||||
|
||||
let auth = build_auth_provider(&config);
|
||||
let server = BasculeServer::with_arc_auth(config, auth, DefaultHandler)?;
|
||||
server.run().await
|
||||
|
|
|
|||
Loading…
Reference in a new issue