From 04e6fec673016c8941a23cbd1fd2524a304acb18 Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Sat, 4 Jul 2026 19:52:30 -0400 Subject: [PATCH] slice-5: RUTSTER_HTTP_BIND env config via new config module Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012QndwfhjyTiZcUYp87dwW8 Signed-off-by: Aaron D. Lee --- crates/rutster/src/config.rs | 51 ++++++++++++++++++++++++++++++++++++ crates/rutster/src/lib.rs | 1 + crates/rutster/src/main.rs | 6 ++++- docs/QUICKSTART.md | 2 +- 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 crates/rutster/src/config.rs diff --git a/crates/rutster/src/config.rs b/crates/rutster/src/config.rs new file mode 100644 index 0000000..ed84062 --- /dev/null +++ b/crates/rutster/src/config.rs @@ -0,0 +1,51 @@ +//! # config — pure env-parsing helpers (slice-5) +//! +//! Every knob is a pure function over `Option` / `&str` so tests +//! never mutate process env (env mutation in tests races across the +//! parallel test harness — the same reason `api_key.rs` needs its +//! ENV_MUTEX). `main.rs` is the only caller that touches `std::env`. +//! +//! Closes 2026-07-04 scalability review "HTTP bind hardcoded" (minor). + +use std::net::SocketAddr; + +/// Resolve the HTTP bind address from `RUTSTER_HTTP_BIND`. +/// +/// `None` → the historical default `0.0.0.0:8080`. Invalid input is a +/// hard error (fail-fast at startup — an operator typo must not silently +/// bind the default and hide behind an LB health check). +pub fn http_bind(raw: Option) -> Result { + match raw { + None => Ok("0.0.0.0:8080".parse().expect("static default parses")), + Some(s) => s + .parse() + .map_err(|e| format!("RUTSTER_HTTP_BIND {s:?} is not a socket address: {e}")), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn http_bind_defaults_when_unset() { + assert_eq!( + http_bind(None).unwrap(), + "0.0.0.0:8080".parse::().unwrap() + ); + } + + #[test] + fn http_bind_parses_override() { + assert_eq!( + http_bind(Some("127.0.0.1:9090".into())).unwrap(), + "127.0.0.1:9090".parse::().unwrap() + ); + } + + #[test] + fn http_bind_rejects_garbage_with_var_name_in_error() { + let err = http_bind(Some("not-an-addr".into())).unwrap_err(); + assert!(err.contains("RUTSTER_HTTP_BIND")); + } +} diff --git a/crates/rutster/src/lib.rs b/crates/rutster/src/lib.rs index 0d89088..3e1e9ee 100644 --- a/crates/rutster/src/lib.rs +++ b/crates/rutster/src/lib.rs @@ -22,6 +22,7 @@ //! - [slice-1 spec §4](../../docs/superpowers/specs/2026-06-28-slice-1-webrtc-loopback-design.md) //! - [ARCHITECTURE.md](../../docs/ARCHITECTURE.md) — fused vertical. +pub mod config; pub mod media_thread; pub mod routes; pub mod session_map; diff --git a/crates/rutster/src/main.rs b/crates/rutster/src/main.rs index b72f0d7..bd3c09f 100644 --- a/crates/rutster/src/main.rs +++ b/crates/rutster/src/main.rs @@ -41,7 +41,11 @@ async fn main() { .spawn_media_thread(tokio::runtime::Handle::current()) .expect("media thread spawn"); - let addr: SocketAddr = "0.0.0.0:8080".parse().expect("valid addr"); + // RUTSTER_HTTP_BIND: per-instance bind is the first thing any LB / + // compose template parametrizes (slice-5; matches the existing + // RUTSTER_TAP_BIND pattern in rutster-brain-realtime). + let addr: SocketAddr = rutster::config::http_bind(std::env::var("RUTSTER_HTTP_BIND").ok()) + .expect("RUTSTER_HTTP_BIND must be host:port"); info!(%addr, "listening"); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); axum::serve(listener, router(app_state)) diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index f866fd2..eb95644 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -83,7 +83,7 @@ RUST_LOG=rutster=debug cargo run | Browser shows no mic prompt | Another tab/app holding the mic, or mic permissions disabled for `localhost`. Check browser settings. | | `ICE connection failed` in the browser | Shouldn't happen on loopback (host candidates only). If it does, check the server console for the str0m error. | | Click Start call, nothing happens | Open the browser console (F12). The page logs ICE state + connection state to a `
` element. Look for the failure there. |
-| Port 8080 already in use | Another process holding the port. Either stop it or edit `crates/rutster/src/main.rs` to bind a different port. |
+| Port 8080 already in use | Set `RUTSTER_HTTP_BIND`, e.g. `RUTSTER_HTTP_BIND=0.0.0.0:8090 cargo run -p rutster` |
 
 The browser test page at `GET /` is a single self-contained HTML file
 with inline JS — no build step. View source to see exactly what the