deploy slice C+D+E (binary features): rustls Phase 1 + /metrics + ValkeyEventSink #25

Merged
alee merged 10 commits from deploy-c/binary-features into docs/deployment-topology-spec 2026-07-06 14:58:44 +00:00
Owner

What lands

  • rustls Phase 1 BYO-cert in-process TLS (RUTSTER_TLS_CERT / RUTSTER_TLS_KEY enable a TLS listener on axum-server 0.8; hot-reload on cert change WITHOUT dropping live WS via RustlsConfig::reload_from_pem_file; TCP_NODELAY parity via custom NodeLayAcceptor wrapping RustlsAcceptor — axum-server 0.8 has NO built-in tcp_nodelay on the TLS path, verified by reading the source).
  • /metrics hand-rolled Prometheus text endpoint on own listener RUTSTER_METRICS_BIND (default 127.0.0.1:9090, never routed through Caddy). Reuses the existing MediaCmd::Stats round-trip (same shape /readyz uses) + adds admission_rejects as a local counter + EventSink::drops() as a new default trait method returning Option<u64> (None for TracingEventSink, Some for ValkeyEventSink). Does NOT promote the media_thread locals to atomics — the round-trip is the proven pattern.
  • ValkeyEventSink (first Valkey consumer; redis crate BSD-3 OK with deny.toml per rescue plan §1; XADD MAXLEN ~ 10000 capped stream; strictly fire-and-forget via bounded mpsc + background task + Arc<AtomicU64> drop counter; RUTSTER_VALKEY_URL unset ⇒ TracingEventSink unchanged).
  • New files: crates/rutster/src/{tls_acceptor.rs, serve_tls.rs, metrics.rs, valkey_sink.rs}. ROOT Cargo.toml: axum-server + redis deps. .github/workflows/ci.yml: valkey service container for the ValkeyEventSink test.

Adopted defaults (rescue plan §1)

  • ValkeyEventSink stream cap: XADD MAXLEN ~ 10000 (~10k events, ~few MB, days-of-calls forensic window; a config const, not a migration). Reversible.
  • "Existing atomics" spec §5.5 wording correction: tick_overruns + last_tick_micros are LOCAL variables, NOT atomics. Reuse the Stats round-trip; do NOT promote locals to atomics.
  • TCP_NODELAY on TLS path: axum-server 0.8 Server builder has NO tcp_nodelay method (verified by reading the source). NodeLayAcceptor is REQUIRED, NOT optional. Matches axum-server's own examples/rustls_session.rs pattern.
  • redis crate BSD-3 license OK with deny.toml.

Implementation decisions during execution

  • redis pinned to 0.27 (NOT 1.3.0): 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.
  • StreamMaxlen::Approx API correction: the plan's verbatim xadd_maxlen call used a bool 2nd arg (wrong). redis 0.27's actual signature takes a StreamMaxlen enum. PM-authorized correction applied.
  • rcgen 0.14 API adaptation: the plan used obsolete Certificate::from_params. Adapted to CertificateParams::self_signed with a generated KeyPair (the rcgen 0.14 API actually available in the Cargo.lock pin).

Out of scope

  • In-binary ACME Phase 2 (deferred per spec §1.2).
  • T3 fleet, k8s manifests, Helm, serverless (deferred per spec §1.2).
  • deploy/ artifacts + docs/deploy/ tree (dev-b + dev-d territory).

Seam invariant

  • crates/rutster-media/src/{loop_driver.rs,rtc_session.rs} byte-identical (hashes 744bf314ed / f47d63b9a2). Plan C touches NO file in crates/rutster-media/src/. The Stats round-trip is the proven pattern; media_thread.rs locals are NOT promoted to atomics. Seam gate stays green.

Merge instructions

  • squash-merge (independent branch, no stacked-branches case in this epoch)
  • DCO signoff every commit (Aaron D. Lee <himself@adlee.work>)
  • after merge: maintainer tags docs/deployment-topology-spec: deploy-c-binary-features-merged
## What lands - rustls Phase 1 BYO-cert in-process TLS (RUTSTER_TLS_CERT / RUTSTER_TLS_KEY enable a TLS listener on axum-server 0.8; hot-reload on cert change WITHOUT dropping live WS via RustlsConfig::reload_from_pem_file; TCP_NODELAY parity via custom NodeLayAcceptor wrapping RustlsAcceptor — axum-server 0.8 has NO built-in tcp_nodelay on the TLS path, verified by reading the source). - /metrics hand-rolled Prometheus text endpoint on own listener RUTSTER_METRICS_BIND (default 127.0.0.1:9090, never routed through Caddy). Reuses the existing MediaCmd::Stats round-trip (same shape /readyz uses) + adds admission_rejects as a local counter + EventSink::drops() as a new default trait method returning Option&lt;u64&gt; (None for TracingEventSink, Some for ValkeyEventSink). Does NOT promote the media_thread locals to atomics — the round-trip is the proven pattern. - ValkeyEventSink (first Valkey consumer; redis crate BSD-3 OK with deny.toml per rescue plan §1; XADD MAXLEN ~ 10000 capped stream; strictly fire-and-forget via bounded mpsc + background task + Arc&lt;AtomicU64&gt; drop counter; RUTSTER_VALKEY_URL unset ⇒ TracingEventSink unchanged). - New files: crates/rutster/src/{tls_acceptor.rs, serve_tls.rs, metrics.rs, valkey_sink.rs}. ROOT Cargo.toml: axum-server + redis deps. .github/workflows/ci.yml: valkey service container for the ValkeyEventSink test. ## Adopted defaults (rescue plan §1) - ValkeyEventSink stream cap: XADD MAXLEN ~ 10000 (~10k events, ~few MB, days-of-calls forensic window; a config const, not a migration). Reversible. - "Existing atomics" spec §5.5 wording correction: tick_overruns + last_tick_micros are LOCAL variables, NOT atomics. Reuse the Stats round-trip; do NOT promote locals to atomics. - TCP_NODELAY on TLS path: axum-server 0.8 Server builder has NO tcp_nodelay method (verified by reading the source). NodeLayAcceptor is REQUIRED, NOT optional. Matches axum-server's own examples/rustls_session.rs pattern. - redis crate BSD-3 license OK with deny.toml. ## Implementation decisions during execution - redis pinned to 0.27 (NOT 1.3.0): 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. - StreamMaxlen::Approx API correction: the plan's verbatim xadd_maxlen call used a bool 2nd arg (wrong). redis 0.27's actual signature takes a StreamMaxlen enum. PM-authorized correction applied. - rcgen 0.14 API adaptation: the plan used obsolete Certificate::from_params. Adapted to CertificateParams::self_signed with a generated KeyPair (the rcgen 0.14 API actually available in the Cargo.lock pin). ## Out of scope - In-binary ACME Phase 2 (deferred per spec §1.2). - T3 fleet, k8s manifests, Helm, serverless (deferred per spec §1.2). - deploy/ artifacts + docs/deploy/ tree (dev-b + dev-d territory). ## Seam invariant - crates/rutster-media/src/{loop_driver.rs,rtc_session.rs} byte-identical (hashes 744bf314edf7f4925c8bb3bd0f5176dbc88f8113 / f47d63b9a2883d37066a93c9daa0e2cf8816bec4). Plan C touches NO file in crates/rutster-media/src/. The Stats round-trip is the proven pattern; media_thread.rs locals are NOT promoted to atomics. Seam gate stays green. ## Merge instructions - squash-merge (independent branch, no stacked-branches case in this epoch) - DCO signoff every commit (Aaron D. Lee &lt;himself@adlee.work&gt;) - after merge: maintainer tags docs/deployment-topology-spec: deploy-c-binary-features-merged
alee added 10 commits 2026-07-06 07:28:30 +00:00
Signed-off-by: Aaron D. Lee <himself@adlee.work>
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>
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>
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>
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>
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>
Signed-off-by: Aaron D. Lee <himself@adlee.work>
alee merged commit 838ecdf5e6 into docs/deployment-topology-spec 2026-07-06 14:58:44 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alee/rutster#25