use axum::extract::State; use axum::response::Json; use axum::routing::get; use axum::Router; use serde_json::{json, Value}; use std::sync::Arc; use std::time::Instant; use tokio::sync::watch; pub struct HealthState { pub started_at: Instant, pub cycle_count: watch::Receiver, pub last_cycle_ok: watch::Receiver, pub db_path: String, } pub async fn start_health_server(port: u16, state: Arc) -> anyhow::Result<()> { let app = Router::new() .route("/health", get(health_handler)) .route("/api/mirror-health", get(health_handler)) .with_state(state); let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)).await?; tracing::info!(port, "health server listening"); axum::serve(listener, app).await?; Ok(()) } async fn health_handler(State(state): State>) -> Json { let uptime = state.started_at.elapsed(); let cycle_count = *state.cycle_count.borrow(); let last_ok = *state.last_cycle_ok.borrow(); Json(json!({ "status": if last_ok || cycle_count == 0 { "ok" } else { "degraded" }, "uptime_secs": uptime.as_secs(), "cycle_count": cycle_count, "last_cycle_ok": last_ok, })) }