feat(server): make rtc.tcp_port configurable via runtime.json + configure RPC #34

Merged
sameh-farouk merged 4 commits from feat/per-user-rtc-tcp-port into main 2026-04-30 04:46:35 +00:00
Member

Closes #33.

Summary

  • Adds rtc_tcp_port field to RuntimeConfig (with #[serde(default)] = 7881)
  • Extends the livekitservice.configure RPC with an rtc_tcp_port: u32 parameter (0 = keep current, same convention as livekit_port/backend_port)
  • render_livekit_yaml reads the value instead of the hardcoded " tcp_port: 7881\n" literal
  • Codegen (rpc_generated.rs, osis_server_generated.rs, openrpc.json) regenerated from the updated livekit.oschema

Why

The existing rtc.ips.includes (PR #32) handles the UDP/7882 multi-user collision by binding per-user node_ip. The TCP-RTC port (default 7881), however, is wildcard-bound (bind_addresses: "0.0.0.0"), so two livekit instances on the same host collide on *:7881 with EADDRINUSE. We can't simply restrict bind_addresses because the wrapper's own twirp_call (rpc.rs:385) hardcodes http://127.0.0.1:<livekit_port> for room-management calls — restricting bind_addresses to node_ip would break that. Per-user TCP port allocation is the cleanest solution.

Verification on multi-user dev box

Two users running concurrently after applying this PR + manual configure:

sameh  livekit-server  bound  *:7885 (slot 5) + [4a0:6976:8fa7:efc:5::1]:7882 UDP
ashraf livekit-server  bound  *:7883 (slot 3) + [4a0:6976:8fa7:efc:3::1]:7882 UDP

No collisions. RPC tested end-to-end: configure with rtc_tcp_port=7889 → runtime.json updated → yaml renders tcp_port: 7889. configure with rtc_tcp_port=0 → keeps existing value.

Compatibility note (READ BEFORE MERGE)

This is a breaking change for callers of livekitservice.configure — the dispatcher requires all named params, and adding rtc_tcp_port makes it required.

Single in-tree caller: service_livekit.nu in lhumina_code/hero_skills. Without a companion 1-line update there, every service_livekit start will fail with "Missing required parameter: rtc_tcp_port".

The companion hero_skills PR is small and trivial — adds rtc_tcp_port: 0 to the params object passed to the configure RPC. Both PRs should land together.

No external SDK clients exist — verified by grepping livekitservice.configure across the org's repos.

Follow-up needed in hero_skills (separate issues / PRs)

  1. PR-1 (compat + cleanup) — required to land alongside this:
    • Add rtc_tcp_port: 0 to service_livekit.nu's cfg_params so the configure RPC stops rejecting calls.
    • Modify svx_lk_wipe_stale_configs to preserve runtime.json (delete only yaml + env). Per-user values then survive --reset / stale-config wipes.
  2. PR-2 (full automation) — depends on PR-1:
    • service_livekit.nu reads [livekit] section from hero_cfg.toml and passes rtc_tcp_port from there to configure RPC.
    • multi_user_add allocates a non-overlapping (livekit_port, rtc_tcp_port, backend_port) triple per user and writes a [livekit] section into the new user's hero_cfg.toml.

Until PR-1 lands: operators can configure per-user ports manually via curl --unix-socket .../rpc.sock to livekitservice.configure. This PR is the necessary foundation; the hero_skills follow-ups close the loop on automation.

Test plan

  • cargo build clean (release)
  • codegen regenerates rpc_generated.rs with the new param
  • configure RPC accepts rtc_tcp_port and persists it to runtime.json
  • configure RPC with rtc_tcp_port: 0 keeps existing value (matches livekit_port/backend_port convention)
  • yaml renders tcp_port: <configured>
  • livekit-server actually binds the configured port (*:7889 test)
  • backwards-compat: old runtime.json without rtc_tcp_port field parses cleanly via serde default
  • two users coexist on same host with non-overlapping TCP ports + per-IP UDP

🤖 Generated with Claude Code

Closes #33. ## Summary - Adds `rtc_tcp_port` field to `RuntimeConfig` (with `#[serde(default)] = 7881`) - Extends the `livekitservice.configure` RPC with an `rtc_tcp_port: u32` parameter (0 = keep current, same convention as `livekit_port`/`backend_port`) - `render_livekit_yaml` reads the value instead of the hardcoded `" tcp_port: 7881\n"` literal - Codegen (`rpc_generated.rs`, `osis_server_generated.rs`, `openrpc.json`) regenerated from the updated `livekit.oschema` ## Why The existing `rtc.ips.includes` (PR #32) handles the UDP/7882 multi-user collision by binding per-user `node_ip`. The TCP-RTC port (default 7881), however, is wildcard-bound (`bind_addresses: "0.0.0.0"`), so two livekit instances on the same host collide on `*:7881` with `EADDRINUSE`. We can't simply restrict `bind_addresses` because the wrapper's own twirp_call (rpc.rs:385) hardcodes `http://127.0.0.1:<livekit_port>` for room-management calls — restricting bind_addresses to node_ip would break that. Per-user TCP port allocation is the cleanest solution. ## Verification on multi-user dev box Two users running concurrently after applying this PR + manual configure: ``` sameh livekit-server bound *:7885 (slot 5) + [4a0:6976:8fa7:efc:5::1]:7882 UDP ashraf livekit-server bound *:7883 (slot 3) + [4a0:6976:8fa7:efc:3::1]:7882 UDP ``` No collisions. RPC tested end-to-end: configure with `rtc_tcp_port=7889` → runtime.json updated → yaml renders `tcp_port: 7889`. configure with `rtc_tcp_port=0` → keeps existing value. ## Compatibility note (READ BEFORE MERGE) This is a **breaking change** for callers of `livekitservice.configure` — the dispatcher requires all named params, and adding `rtc_tcp_port` makes it required. **Single in-tree caller**: `service_livekit.nu` in `lhumina_code/hero_skills`. Without a companion 1-line update there, every `service_livekit start` will fail with `"Missing required parameter: rtc_tcp_port"`. The companion hero_skills PR is small and trivial — adds `rtc_tcp_port: 0` to the params object passed to the configure RPC. Both PRs should land together. No external SDK clients exist — verified by grepping `livekitservice.configure` across the org's repos. ## Follow-up needed in hero_skills (separate issues / PRs) 1. **PR-1 (compat + cleanup)** — required to land alongside this: - Add `rtc_tcp_port: 0` to service_livekit.nu's `cfg_params` so the configure RPC stops rejecting calls. - Modify `svx_lk_wipe_stale_configs` to preserve `runtime.json` (delete only yaml + env). Per-user values then survive `--reset` / stale-config wipes. 2. **PR-2 (full automation)** — depends on PR-1: - service_livekit.nu reads `[livekit]` section from `hero_cfg.toml` and passes `rtc_tcp_port` from there to configure RPC. - `multi_user_add` allocates a non-overlapping `(livekit_port, rtc_tcp_port, backend_port)` triple per user and writes a `[livekit]` section into the new user's `hero_cfg.toml`. Until PR-1 lands: operators can configure per-user ports manually via `curl --unix-socket .../rpc.sock` to `livekitservice.configure`. This PR is the necessary foundation; the hero_skills follow-ups close the loop on automation. ## Test plan - [x] cargo build clean (release) - [x] codegen regenerates `rpc_generated.rs` with the new param - [x] configure RPC accepts `rtc_tcp_port` and persists it to runtime.json - [x] configure RPC with `rtc_tcp_port: 0` keeps existing value (matches livekit_port/backend_port convention) - [x] yaml renders `tcp_port: <configured>` - [x] livekit-server actually binds the configured port (`*:7889` test) - [x] backwards-compat: old runtime.json without `rtc_tcp_port` field parses cleanly via serde default - [x] two users coexist on same host with non-overlapping TCP ports + per-IP UDP 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(server): make rtc.tcp_port configurable via runtime.json + configure RPC
Some checks failed
Build & Test / check (pull_request) Failing after 3s
d66ab1defd
Adds rtc_tcp_port to RuntimeConfig (with serde defaulting so older
runtime.json files continue to parse) and as a parameter on the
livekitservice.configure RPC. The yaml renderer reads the configured
value instead of the hardcoded 7881 literal.

Why: multi-user setups need non-overlapping TCP-RTC ports per user.
The existing rtc.ips.includes (PR #32) handles UDP/7882 by binding
per-user node_ip — but TCP/7881 is wildcard-bound (bind_addresses:
0.0.0.0), so two livekit instances on the same host collide on
*:7881 with EADDRINUSE. With this change, operators can allocate
non-overlapping per-user TCP ports.

Closes #33.

Backwards-compatible:
- Old runtime.json files (no rtc_tcp_port field) parse via
  #[serde(default = "default_rtc_tcp_port")] = 7881
- configure RPC: rtc_tcp_port=0 means keep current value, same
  convention as livekit_port and backend_port

Verification on multi-user dev box:
- sameh livekit-server bound *:7885 (slot 5) + [5::1]:7882 UDP
- ashraf livekit-server bound *:7883 (slot 3) + [3::1]:7882 UDP
- Both running concurrently, no collisions

Follow-up needed in hero_skills (separate issue):
- service_livekit.nu's svx_lk_wipe_stale_configs preserves
  runtime.json so per-user values survive --reset. One-line change.
- service_livekit.nu reads [livekit] section from hero_cfg.toml and
  passes rtc_tcp_port to configure RPC instead of hardcoded 0.
- multi_user_add allocates non-overlapping per-user port triples and
  writes [livekit] into hero_cfg.toml on user creation.

Until those hero_skills changes land, operators set per-user values
manually via curl --unix-socket .../rpc.sock to livekitservice.configure.
This PR is the necessary foundation; both follow-ups depend on it.
fixup: update hero_livekit_rhai bindings + example for new rtc_tcp_port param
Some checks failed
Build & Test / check (pull_request) Failing after 2s
6b91b2d965
fix(rpc): make configure rtc_tcp_port optional for old-skill compat
Some checks failed
Build & Test / check (pull_request) Failing after 3s
5d35aac039
Empirical compat test (old hero_skills + new hero_livekit binary) failed with
`error -32000: Missing required parameter: rtc_tcp_port` — the OpenRPC
dispatcher rejects calls without the field before serde defaults can apply.
Mark rtc_tcp_port optional in livekit.oschema (`?:`) so the dispatcher and
generated client both treat it as omittable. Handler now accepts Option<u32>
and ignores None / Some(0) (preserving the "keep current value" semantic for
livekit_port and backend_port). Rhai bindings keep their 8-arg surface and map
i64=0 to None on the wire.
Merge branch 'main' into feat/per-user-rtc-tcp-port
Some checks failed
Build & Test / check (pull_request) Failing after 2s
419fa98b96
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_livekit!34
No description provided.