From e435f7d7b4ad4e4b1d3c21c35df5f41ffd642376 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 26 May 2026 18:01:30 +0530 Subject: Add HTTP health endpoint on /health and /api/mirror-health - New axum-based health server on port 7335 (configurable via health_port) - Reports status, uptime, cycle_count, last_cycle_ok as JSON - Status is 'ok' on startup and after successful cycles, 'degraded' after failures - Config: storage.health_port defaults to 7335 - Spawned alongside daemon loop, independent of mirror cycles --- src/http_health.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/http_health.rs (limited to 'src/http_health.rs') diff --git a/src/http_health.rs b/src/http_health.rs new file mode 100644 index 0000000..0cdfeb5 --- /dev/null +++ b/src/http_health.rs @@ -0,0 +1,40 @@ +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, + })) +} -- cgit v1.2.3