From 02165cb84b2914aaedb64e22cb80ef641edd02ce Mon Sep 17 00:00:00 2001 From: dawn <90008@gaze.systems> Date: Mon, 2 Feb 2026 06:44:53 +0300 Subject: [PATCH] [api] put debug api in a different router that we bind on a different port, disabled by default --- src/api/debug.rs | 12 +++++------- src/api/mod.rs | 25 ++++++++++++++++++++++++- src/config.rs | 13 +++++++++++++ src/main.rs | 19 ++++++++++--------- tests/common.nu | 2 ++ tests/repo_sync_integrity.nu | 7 ++++--- 6 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/api/debug.rs b/src/api/debug.rs index 2212c30..0bd6a24 100644 --- a/src/api/debug.rs +++ b/src/api/debug.rs @@ -1,13 +1,12 @@ use crate::api::AppState; use crate::db::keys; use axum::{ - extract::{ConnectInfo, Query, State}, + extract::{Query, State}, http::StatusCode, Json, }; use jacquard::types::ident::AtIdentifier; use serde::{Deserialize, Serialize}; -use std::net::SocketAddr; use std::sync::Arc; #[derive(Deserialize)] @@ -21,15 +20,14 @@ pub struct DebugCountResponse { pub count: usize, } +pub fn router() -> axum::Router> { + axum::Router::new().route("/debug/count", axum::routing::get(handle_debug_count)) +} + pub async fn handle_debug_count( State(state): State>, - ConnectInfo(addr): ConnectInfo, Query(req): Query, ) -> Result, StatusCode> { - if !addr.ip().is_loopback() { - return Err(StatusCode::FORBIDDEN); - } - let did = state .resolver .resolve_did(&AtIdentifier::new(req.did.as_str()).map_err(|_| StatusCode::BAD_REQUEST)?) diff --git a/src/api/mod.rs b/src/api/mod.rs index 88d7340..9bc5a27 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -15,7 +15,6 @@ pub async fn serve(state: Arc, port: u16) -> miette::Result<()> { .route("/health", get(|| async { "OK" })) .route("/stats", get(stats::get_stats)) .route("/stream", get(stream::handle_stream)) - .route("/debug/count", get(debug::handle_debug_count)) .merge(xrpc::router()) .merge(repo::router()) .with_state(state) @@ -37,3 +36,27 @@ pub async fn serve(state: Arc, port: u16) -> miette::Result<()> { Ok(()) } + +pub async fn serve_debug(state: Arc, port: u16) -> miette::Result<()> { + let app = debug::router() + .with_state(state) + .layer(TraceLayer::new_for_http()); + + let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}")) + .await + .map_err(|e| miette::miette!("failed to bind debug server to port {port}: {e}"))?; + + tracing::info!( + "Debug server listening on {}", + listener.local_addr().unwrap() + ); + + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .await + .map_err(|e| miette::miette!("debug server error: {e}"))?; + + Ok(()) +} diff --git a/src/config.rs b/src/config.rs index 376d22f..6eb9909 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,6 +18,8 @@ pub struct Config { pub cache_size: u64, pub backfill_concurrency_limit: usize, pub disable_lz4_compression: bool, + pub debug_port: u16, + pub enable_debug: bool, } impl Config { @@ -72,6 +74,15 @@ impl Config { .map(|v| v == "true") .unwrap_or(false); + let debug_port = env::var("HYDRANT_DEBUG_PORT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(3001); + + let enable_debug = env::var("HYDRANT_ENABLE_DEBUG") + .map(|v| v == "true") + .unwrap_or(false); + Ok(Self { database_path, relay_host, @@ -84,6 +95,8 @@ impl Config { cache_size, backfill_concurrency_limit, disable_lz4_compression, + debug_port, + enable_debug, }) } } diff --git a/src/main.rs b/src/main.rs index b920b1e..0b37fd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,15 +40,16 @@ async fn main() -> miette::Result<()> { let (state, backfill_rx, buffer_rx) = AppState::new(&cfg)?; let state = Arc::new(state); - tokio::spawn({ - let port = cfg.api_port; - let state = state.clone(); - async move { - if let Err(e) = api::serve(state, port).await { - error!("API server failed: {e}"); - } - } - }); + tokio::spawn( + api::serve(state.clone(), cfg.api_port).inspect_err(|e| error!("API server failed: {e}")), + ); + + if cfg.enable_debug { + tokio::spawn( + api::serve_debug(state.clone(), cfg.debug_port) + .inspect_err(|e| error!("debug server failed: {e}")), + ); + } tokio::spawn({ let state = state.clone(); diff --git a/tests/common.nu b/tests/common.nu index 8923849..f4c0b1a 100644 --- a/tests/common.nu +++ b/tests/common.nu @@ -15,6 +15,8 @@ export def start-hydrant [binary: string, db_path: string, port: int] { HYDRANT_DATABASE_PATH: ($db_path), HYDRANT_FULL_NETWORK: "false", HYDRANT_API_PORT: ($port | into string), + HYDRANT_ENABLE_DEBUG: "true", + HYDRANT_DEBUG_PORT: ($port + 1 | into string), HYDRANT_LOG_LEVEL: "debug" } { sh -c $"($binary) >($log_file) 2>&1 & echo $!" | str trim | into int diff --git a/tests/repo_sync_integrity.nu b/tests/repo_sync_integrity.nu index 7315019..8b57bf7 100755 --- a/tests/repo_sync_integrity.nu +++ b/tests/repo_sync_integrity.nu @@ -55,7 +55,7 @@ export def check-consistency [hydrant_url: string, pds_url: string, did: string] } # verify countRecords API against debug endpoint -def check-count [hydrant_url: string, did: string] { +def check-count [hydrant_url: string, debug_url: string, did: string] { print "verifying countRecords API..." let collections = [ "app.bsky.feed.post" @@ -77,7 +77,7 @@ def check-count [hydrant_url: string, did: string] { # 2. get actual scan count from debug endpoint let debug_count = try { - (http get $"($hydrant_url)/debug/count?did=($did)&collection=($coll)").count + (http get $"($debug_url)/debug/count?did=($did)&collection=($coll)").count } catch { print $" error calling debug count for ($coll)" return false @@ -98,6 +98,7 @@ def main [] { let pds = "https://zwsp.xyz" let port = 3001 let url = $"http://localhost:($port)" + let debug_url = $"http://127.0.0.1:($port + 1)" let db_path = (mktemp -d -t hydrant_test.XXXXXX) print $"testing backfill integrity for ($did)..." @@ -116,7 +117,7 @@ def main [] { if (wait-for-backfill $url) { # Run both consistency checks let integrity_passed = (check-consistency $url $pds $did) - let count_passed = (check-count $url $did) + let count_passed = (check-count $url $debug_url $did) if $integrity_passed and $count_passed { print "all integrity checks passed!" -- 2.51.2