chore(deploy): add optional LiveKit fallback

This commit is contained in:
AAA committed 2026-08-19 12:38:33 +08:00
1 parent 96b2d4f1e6
commit 628ac7c46d
5 files changed
+197 -3

No files matched your search

+15
View File
@@ -19,6 +19,15 @@ MAX_VIEWERS_PER_ROOM=8
# Experimental standard-WebRTC relay chains. Keep false outside measured tests.
PEER_ASSISTED_MEDIA=false
# Optional automatic SFU fallback capacity. Set PEER_ASSISTED_MEDIA=true and
# supply all three values together; leaving them empty keeps LiveKit completely
# dormant. Production requires wss and an independent API secret of 32+ bytes.
LIVEKIT_URL=
LIVEKIT_API_KEY=
LIVEKIT_API_SECRET=
# Effective only when the complete LiveKit tuple is configured. Valid range: 1-2.
MAX_SFU_ROOTS_PER_ROOM=2
# ICE servers. Separate multiple URLs with commas.
STUN_URLS=
TURN_URLS=
@@ -31,6 +40,12 @@ TURN_CREDENTIAL_TTL_SECONDS=3600
# STUN_URLS=stun:turn.example.com:3478
# TURN_URLS=turn:turn.example.com:3478?transport=udp,turn:turn.example.com:3478?transport=tcp
# TURN_SHARED_SECRET=replace-with-at-least-32-random-bytes
# Optional automatic SFU fallback (requires PEER_ASSISTED_MEDIA=true and does
# not replace STUN/TURN above):
# LIVEKIT_URL=wss://share.example.com
# LIVEKIT_API_KEY=replace-with-a-livekit-api-key
# LIVEKIT_API_SECRET=replace-with-at-least-32-independent-random-bytes
# MAX_SFU_ROOTS_PER_ROOM=2
# Optional TURN/TLS (standard port; append to TURN_URLS when configured):
# turns:turn.example.com:5349?transport=tcp
# Port 443 is an optional restrictive-network enhancement, not the default.
+27
View File
@@ -0,0 +1,27 @@
# Minimal single-node LiveKit capacity for Screener's optional automatic SFU
# fallback. Copy this file to an untracked service-owned location such as
# /etc/livekit/livekit.yaml. Store only the generated key mapping in
# /etc/livekit/keys.yaml, owned by the livekit service user with mode 0600.
port: 7880
key_file: /etc/livekit/keys.yaml
rtc:
# These media ports must be reached directly by clients. Port 7880 is only
# for the same-host reverse proxy and must be denied by every public firewall.
tcp_port: 7881
udp_port: 7882
use_external_ip: true
room:
auto_create: true
# Screener enforces its configured admission limit before issuing a token.
# This server-side ceiling covers the repository's maximum 16 viewers + host.
max_participants: 17
logging:
level: info
pion_level: error
# Redis, embedded TURN, recording, ingress, egress, and webhooks are omitted on
# purpose. This first fallback deployment is one process on one node.
@@ -37,6 +37,47 @@ server {
add_header X-Frame-Options "DENY" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Optional LiveKit signaling for automatic SFU fallback. Set LIVEKIT_URL to
# wss://share.bonfire.icu. Legacy clients use the exact /rtc endpoint.
location = /rtc {
# The WebSocket query contains a short-lived LiveKit join token. Do not
# persist the request target in either access or error logs.
access_log off;
error_log /dev/null crit;
proxy_pass http://127.0.0.1:7880;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $screener_connection_upgrade;
proxy_read_timeout 5h;
proxy_send_timeout 5h;
proxy_buffering off;
}
# Current clients use versioned paths below /rtc/.
location ^~ /rtc/ {
# The WebSocket query contains a short-lived LiveKit join token. Do not
# persist the request target in either access or error logs.
access_log off;
error_log /dev/null crit;
proxy_pass http://127.0.0.1:7880;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $screener_connection_upgrade;
proxy_read_timeout 5h;
proxy_send_timeout 5h;
proxy_buffering off;
}
location / {
proxy_pass http://127.0.0.1:8787;
proxy_http_version 1.1;
+39
View File
@@ -0,0 +1,39 @@
[Unit]
Description=LiveKit single-node SFU fallback for Screener
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=livekit
Group=livekit
ExecStartPre=/usr/bin/test -r /etc/livekit/livekit.yaml
ExecStartPre=/usr/bin/test -r /etc/livekit/keys.yaml
ExecStart=/usr/local/bin/livekit-server --config /etc/livekit/livekit.yaml
Restart=on-failure
RestartSec=3s
TimeoutStopSec=20s
UMask=0077
LimitNOFILE=65535
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
CapabilityBoundingSet=
SystemCallArchitectures=native
[Install]
WantedBy=multi-user.target
+75 -3
View File
@@ -8,6 +8,11 @@ Caddy or nginx; coturn is the separate STUN/TURN service. Normal media remains
browser-to-browser. Only an ICE pair that cannot connect directly consumes TURN
bandwidth.
A deployment may additionally provide one single-node LiveKit process as the
automatic final media fallback. This capacity is dormant unless the complete
`LIVEKIT_URL`/key/secret tuple is configured. It does not replace the P2P path,
the peer-assisted experiment, or the required application STUN/TURN settings.
The public staging infrastructure has already passed the relay checks recorded
in `docs/status.md`, but every new deployment must run the direct, relay, and
mixed-network procedures below. Configuration validation alone is not evidence
@@ -52,6 +57,12 @@ explicitly sets `LISTEN_HOST=127.0.0.1` because only the same-host reverse proxy
should connect. A container can instead use `0.0.0.0` and enforce the intended
boundary with port publishing rules or a host firewall.
The optional same-host LiveKit layout needs no additional public hostname:
`LIVEKIT_URL=wss://share.example.com` and nginx forwards only the exact `/rtc`
endpoint and paths below `/rtc/` to LiveKit. Its HTTP/WebSocket listener on TCP
7880 is private to nginx, while WebRTC media reaches LiveKit directly on TCP
7881 or UDP 7882.
## Production application environment
Keep the real values in the process secret store or an untracked, access-restricted
@@ -83,6 +94,16 @@ TURN_SHARED_SECRET=<SAME_VALUE_AS_COTURN_STATIC_AUTH_SECRET>
TURN_CREDENTIAL_TTL_SECONDS=3600
```
To make automatic SFU fallback capacity available, add the complete tuple:
```dotenv
PEER_ASSISTED_MEDIA=true
LIVEKIT_URL=wss://share.example.com
LIVEKIT_API_KEY=<GENERATED_LIVEKIT_API_KEY>
LIVEKIT_API_SECRET=<INDEPENDENT_SECRET_OF_AT_LEAST_32_BYTES>
MAX_SFU_ROOTS_PER_ROOM=2
```
`ALLOWED_ORIGINS` must list exact `http` or `https` origins, never `*`.
`ACCESS_PASSWORD` is optional: omit it or leave it empty for a public site. A
non-empty value must contain 1 through 128 visible ASCII characters (`0x21`
@@ -101,6 +122,16 @@ browsers receive HMAC-SHA1-derived, time-limited credentials after room
authentication. Keep host clocks synchronized because the credential username
contains its Unix expiry time.
The three `LIVEKIT_*` values must either all be absent or all be present, and a
complete tuple requires `PEER_ASSISTED_MEDIA=true`. An empty tuple keeps the
optional SDK and server path dormant. `LIVEKIT_URL` must be a plain `ws:` or
`wss:` origin with no `/rtc` suffix; production requires `wss:`.
`LIVEKIT_API_SECRET` must contain at least 32 bytes and must not reuse
`ACCESS_PASSWORD` or `TURN_SHARED_SECRET`. `MAX_SFU_ROOTS_PER_ROOM` defaults to
2 and accepts only 1 or 2; it is ignored when LiveKit is not configured. These
credentials authorize short-lived LiveKit room tokens and do not provide E2EE:
the LiveKit operator can access ordinary SFU media.
When `ACCESS_PASSWORD` is configured, both host and viewer routes first show the
same login gate. Only `POST /api/session` accepts the password in an
`Authorization: Bearer` header, then returns a 12-hour stateless HMAC-SHA256
@@ -192,7 +223,11 @@ location / {
The tracked
[`share.bonfire.icu.conf.example`](../deploy/nginx/share.bonfire.icu.conf.example)
follows this simple model and listens directly on public TCP 443. It does not
depend on the optional SNI router.
depend on the optional SNI router. Its optional exact `/rtc` and `/rtc/` prefix
locations preserve the LiveKit request URI and disable both access logging and
request-line error logging. Legacy `/rtc?token=...` and current versioned
requests carry a short-lived LiveKit JWT in the query string, so neither request
target may fall through to the site's ordinary logs.
When Certbot manages the Web certificate, install the tracked
[`reload-nginx.sh`](../deploy/certbot/reload-nginx.sh) as an executable under
@@ -213,6 +248,39 @@ TURN reachability, or any external dependency, so use it only to decide whether
the Node process can accept HTTP requests. It is not a deployment-readiness or
end-to-end media check.
## Optional single-node LiveKit fallback
Use the tracked
[`livekit.yaml.example`](../deploy/livekit/livekit.yaml.example) and
[`livekit.service.example`](../deploy/systemd/livekit.service.example) as the
minimal single-node baseline. Install a reviewed, pinned LiveKit Server release
from its official release assets, generate an independent key pair with
`livekit-server generate-keys`, and place only the YAML key mapping in
`/etc/livekit/keys.yaml`. Keep that file owned by the `livekit` service account
with mode `0600`; put the same values in Screener's untracked process secrets.
The example deliberately omits Redis and every recording, ingress, egress,
webhook, and embedded-TURN service. Do not add Redis for this one-node workload.
The service journal is the diagnostic log; keep its retention finite and access
restricted. Never enable debug/Pion packet logging continuously or persist JWTs,
SDP, ICE candidates, API secrets, or full `/rtc` and `/rtc/*` request targets.
Configure both the host firewall and the provider firewall independently:
| Destination | Protocol | Exposure | Purpose |
| --- | --- | --- | --- |
| `127.0.0.1:7880` | TCP | Private | nginx to LiveKit signaling/API |
| public host `:7881` | TCP | Public | WebRTC ICE/TCP fallback |
| public host `:7882` | UDP | Public | WebRTC ICE/UDP mux |
LiveKit may listen beyond loopback while it discovers and advertises public
media addresses, so the public-firewall denial of TCP 7880 is mandatory; nginx
must be its only ingress. TCP 7881 and UDP 7882 are direct media listeners and
must not be placed behind the HTTP reverse proxy. If the host is behind NAT,
forward them without port translation and verify the advertised candidate from
an external network. LiveKit ICE/TCP is not equivalent to authenticated TURN;
strict-network SFU reachability remains unproven until separately tested.
## coturn
Copy [`deploy/coturn/turnserver.conf.example`](../deploy/coturn/turnserver.conf.example)
@@ -262,6 +330,8 @@ Open only these public listeners:
| `turn.example.com:49152-49251` | UDP, bidirectional | Relayed media endpoints |
| `turn.example.com:5349` | TCP, optional | Standard TURN/TLS |
| `turn.example.com:443` | TCP, optional | TURN/TLS on a dedicated IP or validated L4/SNI route |
| host public address `:7881` | TCP, optional SFU | LiveKit WebRTC ICE/TCP |
| host public address `:7882` | UDP, optional SFU | LiveKit WebRTC ICE/UDP mux |
The 100-port relay range is an initial small-room limit, not a universal sizing
rule. Monitor 508/allocation failures and concurrent allocations before widening
@@ -318,5 +388,7 @@ References: [coturn 4.17.2 release](https://github.com/coturn/coturn/releases/ta
the [official example configuration](https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf),
[TURN URI scheme RFC 7065](https://www.rfc-editor.org/rfc/rfc7065.html),
[Cloudflare network-port guidance](https://developers.cloudflare.com/fundamentals/reference/network-ports/),
[RFC6265bis](https://datatracker.ietf.org/doc/draft-ietf-httpbis-rfc6265bis/), and
[Node.js Crypto](https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options).
[RFC6265bis](https://datatracker.ietf.org/doc/draft-ietf-httpbis-rfc6265bis/),
[Node.js Crypto](https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options),
[LiveKit firewall guidance](https://docs.livekit.io/deploy/admin/firewall/), and
the [official LiveKit configuration sample](https://github.com/livekit/livekit/blob/master/config-sample.yaml).