Implement a bounded-mpsc + background-task Valkey event sink:
- XADD lifecycle events to a capped stream (MAXLEN ~ 10000).
- Fire-and-forget emit(): one mpsc::try_send, count-and-drop on full.
- Arc<AtomicU64> drop counter surfaced in /metrics.
- main.rs selects ValkeyEventSink when RUTSTER_VALKEY_URL is set and
falls back to TracingEventSink otherwise.
- config::valkey_url validates redis:// / rediss://.
- CI services:valkey: block on test and clippy jobs.
Signed-off-by: Aaron D. Lee <himself@adlee.work>
redis 1.3.0 requires Rust 1.88, conflicting with the workspace MSRV of
1.85 (rust-toolchain.toml). redis 0.27.6 has MSRV 1.70, well within
1.85. The xadd_maxlen + StreamMaxlen::Approx API is identical between
0.27 and 1.3 — no kimi-worker T8 code changes needed beyond the version
pin. BSD-3-Clause license unchanged.
Signed-off-by: Aaron D. Lee <himself@adlee.work>
- Add crates/rutster/src/metrics.rs with metrics_router(),
metrics_handler, and render_prometheus_text().
- Add config::metrics_bind() parser for RUTSTER_METRICS_BIND
(default 127.0.0.1:9090).
- Spawn the /metrics listener in main.rs on its own tokio task,
above the TLS/plaintext branch.
- Add integration tests covering the 503 path and the live media
thread 200 path.
- Emit rutster_event_sink_drops_total only when the sink reports a
concrete count; omitting the line avoids misleading zero-drops readings.
Signed-off-by: Aaron D. Lee <himself@adlee.work>
main.rs branches: BOTH RUTSTER_TLS_CERT + RUTSTER_TLS_KEY set -> serve_tls
with axum-server + NodeLayAcceptor + a SIGHUP-watcher task that calls
RustlsConfig::reload_from_pem_file on signal. Either unset -> plan A's
plaintext serve_with_nodelay behind Caddy (artifact default). Partial
config (one set, one unset) panics at boot with a clear message.
Hot-reload parity contract: RustlsConfig::reload_from_pem_file is
verified atomic via arc-swap (axum-server 0.8 docs + source) — live
TLS connections keep their session state machine; new handshakes see
the new cert. The e2e reload test (serve_tls_hot_reload.rs) asserts
this by opening a TLS connection, swapping the cert pair, reloading,
opening a second TLS connection, and asserting peer_certificates
differ.
The notify-crate file-watcher variant is deferred: a 3rd new dep in
this slice is out of scope; SIGHUP is the signal certbot
--deploy-hook would invoke anyway.
Signed-off-by: Aaron D. Lee <himself@adlee.work>
axum_server 0.8's Server builder exposes no tcp_nodelay method (verified
in source: src/axum_server/server.rs:32-360). Plan A made TCP_NODELAY
unconditional on the plaintext path; this slice extends parity to the TLS
path via a custom Accept<TcpStream, S> impl that calls set_nodelay(true)
BEFORE the inner RustlsAcceptor runs the TLS handshake. TLS does not undo
the option (kernel persists it on the fd across the bytes the TLS layer
writes); axum-server's own examples/rustls_session.rs documents this
wrapping pattern for a different reason.
serve_tls_with_nodelay is the production TLS serve path: axum_server::bind
+ NodeLayAcceptor + Handle::graceful_shutdown. main.rs switches to it
when RUTSTER_TLS_CERT + RUTSTER_TLS_KEY are both set (Task 4). Hot-reload
via RustlsConfig::reload_from_pem_file lands in Task 4's e2e reload test.
Signed-off-by: Aaron D. Lee <himself@adlee.work>