feat: hard cross-client voice channel limits via voice-limit-guard
Add a fail-open Python sidecar (livekit/voice-limit-guard.py) that fronts lk-jwt-service to enforce per-room voice participant caps for ALL Matrix clients, not just Lotus Chat: - lk-jwt-service moved to :8071 (systemd drop-in), guard owns :8070 so NPM's existing /sfu/get + /get_token proxy targets are unchanged - guard reads io.lotus.voice_limit.max_users (Synapse admin API, cached), forwards to lk-jwt-service, and on an issued token decodes the LiveKit alias + requester, counts distinct Matrix users via LiveKit ListParticipants, and returns 403 when the room is full (rejoins/extra devices allowed) - any error fails open (returns upstream response) so calls never break - systemd/voice-limit-guard.service; README documents ports, setup, revert Also update landing page: voice limit is now server-enforced for all clients. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,7 +67,8 @@ matrix/
|
||||
- coturn config: `/etc/turnserver.conf`
|
||||
- LiveKit config: `/etc/livekit/config.yaml`
|
||||
- LiveKit service: `livekit-server.service`
|
||||
- lk-jwt-service: `lk-jwt-service.service` (binds `:8070`, serves JWT tokens for MatrixRTC at `/sfu/get` and legacy `/get_token`)
|
||||
- lk-jwt-service: `lk-jwt-service.service` (now binds `:8071` via drop-in `/etc/systemd/system/lk-jwt-service.service.d/override.conf`; serves JWT tokens for MatrixRTC at `/sfu/get` and legacy `/get_token`)
|
||||
- voice-limit-guard: `voice-limit-guard.service` (binds `:8070`, fronts lk-jwt-service — enforces hard per-room voice participant limits for ALL clients; script `/opt/voice-limit-guard/voice-limit-guard.py`) — see [Voice Channel Limits](#voice-channel-limits)
|
||||
- Hookshot: `/opt/hookshot/`, service: `matrix-hookshot.service`
|
||||
- Hookshot config: `/opt/hookshot/config.yml`
|
||||
- Hookshot registration: `/etc/matrix-synapse/hookshot-registration.yaml`
|
||||
@@ -187,6 +188,49 @@ Killing livekit-server while a call is active drops everyone. Instead:
|
||||
|
||||
---
|
||||
|
||||
## Voice Channel Limits
|
||||
|
||||
Per-room voice participant caps are enforced **server-side for every client** (Element, FluffyChat, Lotus Chat, …), not just our own web client.
|
||||
|
||||
**How it works**
|
||||
|
||||
Every Matrix client must fetch a LiveKit JWT from lk-jwt-service before it can join a call. `voice-limit-guard` (a small fail-open Python sidecar, `livekit/voice-limit-guard.py` in this repo) sits in front of that service:
|
||||
|
||||
- lk-jwt-service was moved off `:8070` to `:8071` (systemd drop-in). The guard now owns `:8070`, so NPM's existing `/sfu/get` + `/get_token` proxy targets are unchanged.
|
||||
- On each token request the guard reads `io.lotus.voice_limit` → `max_users` for the room (Synapse admin API, cached 10 s). `0` / absent = no limit.
|
||||
- It forwards the request to lk-jwt-service, and if a token is issued it decodes the JWT to get the LiveKit alias (`video.room`) + requester identity (`sub`), then asks LiveKit `ListParticipants` how many **distinct Matrix users** are in the room.
|
||||
- requester already present (rejoin / extra device) → allow
|
||||
- distinct users ≥ limit → **403** (the client cannot get a token, so it cannot join)
|
||||
- otherwise → allow
|
||||
- **Fail-open:** any error (admin API down, bad token, LiveKit unreachable) returns the upstream response unchanged, so calls keep working even if enforcement is degraded.
|
||||
|
||||
**Setting a limit:** room admins set it from Lotus Chat → Room Settings → General → **Voice** (writes the `io.lotus.voice_limit` state event). Any tool that can send room state works too:
|
||||
|
||||
```bash
|
||||
# max 5 participants in <roomId>; send {} to remove the limit
|
||||
curl -X PUT -H "Authorization: Bearer <admin_token>" -H "Content-Type: application/json" \
|
||||
"https://matrix.lotusguild.org/_matrix/client/v3/rooms/<roomId>/state/io.lotus.voice_limit/" \
|
||||
-d '{"max_users": 5}'
|
||||
```
|
||||
|
||||
**Config:** the guard reads `MATRIX_TOKEN` (server-admin) from `/etc/matrix-deploy.env`; LiveKit key/secret + ports are set in `systemd/voice-limit-guard.service`.
|
||||
|
||||
**Manual (re)deploy** (the file-specific auto-deploy pipeline does not cover this service):
|
||||
|
||||
```bash
|
||||
# On LXC 151
|
||||
install -D -m644 /opt/matrix-config/livekit/voice-limit-guard.py /opt/voice-limit-guard/voice-limit-guard.py
|
||||
install -m644 /opt/matrix-config/systemd/voice-limit-guard.service /etc/systemd/system/voice-limit-guard.service
|
||||
# one-time: rebind lk-jwt-service to :8071
|
||||
mkdir -p /etc/systemd/system/lk-jwt-service.service.d
|
||||
printf '[Service]\nEnvironment=LIVEKIT_JWT_BIND=:8071\n' > /etc/systemd/system/lk-jwt-service.service.d/override.conf
|
||||
systemctl daemon-reload && systemctl restart lk-jwt-service && systemctl enable --now voice-limit-guard
|
||||
```
|
||||
|
||||
**To fully revert** (back to lk-jwt-service directly on `:8070`): `systemctl disable --now voice-limit-guard`, remove the drop-in, `daemon-reload`, `systemctl restart lk-jwt-service`.
|
||||
|
||||
---
|
||||
|
||||
## Access Token Rotation
|
||||
|
||||
The `MATRIX_TOKEN` in `/etc/matrix-deploy.env` on LXC 151 is a Jared user token used to push hookshot transforms to Matrix room state (requires power level ≥ 50 in Spam and Stuff).
|
||||
@@ -230,7 +274,8 @@ The token in `draupnir/production.yaml` in this repo is **intentionally redacted
|
||||
| 6789 | LiveKit metrics | 0.0.0.0 |
|
||||
| 7880 | LiveKit HTTP | 0.0.0.0 |
|
||||
| 7881 | LiveKit RTC TCP | 0.0.0.0 |
|
||||
| 8070 | lk-jwt-service | 0.0.0.0 |
|
||||
| 8070 | voice-limit-guard (fronts lk-jwt-service) | 0.0.0.0 |
|
||||
| 8071 | lk-jwt-service (behind guard) | 0.0.0.0 |
|
||||
| 8080 | synapse-admin (nginx) | 0.0.0.0 |
|
||||
| 3478 | coturn STUN/TURN | 0.0.0.0 |
|
||||
| 5349 | coturn TURNS/TLS | 0.0.0.0 |
|
||||
|
||||
Reference in New Issue
Block a user