Skip to content

The SFU (upstream Galene)

Beep's SFU is unmodified upstream Galene, pinned to a specific commit. No patches, no fork.

make galene-bin     # clone + build the pinned ref into vendor/galene/

The ref lives in one place, GALENE_REF in the Makefile, and the same value is baked into ops/galene/Dockerfile for production images. Keep the two in step when bumping.

Its entire configuration

Two files, in ops/galene/:

groups/meetings.json

{
  "auto-subgroups": true,
  "authKeys": [{"kty": "oct", "alg": "HS256", "k": "<base64url of 32 bytes>"}]
}

Eight lines, and they do all the work:

  • auto-subgroups makes every meetings/<slug> a joinable room the moment somebody joins it. Galene's description lookup walks up the path — meetings/room-abc falls back to meetings.json — and the subgroup inherits the parent's whole description, authKeys included. No per-room file is ever written, so the SFU needs no writable directory at runtime.
  • authKeys is the key the backend signs join tokens with. Since sub and permissions come out of the token, no user or password directory is needed.

k must match the backend's GALENE_AUTH_KEY. The checked-in dev value is base64url of the 32 ASCII bytes 0123456789abcdef0123456789abcdef — obviously not for any deployment.

data/config.json

{} in dev. With canonicalHost unset, Galene accepts a token whose aud names any host, which is what lets the browser reach the SFU through the Vite proxy on :5173 while the token says :8443.

In production set it:

{
  "canonicalHost": "meet.example.com",
  "allowOrigin": ["https://meet.example.com"]
}

Cross-origin WebSocket upgrades

Galene's CheckOrigin rejects a cross-origin /ws handshake with 403 unless allowOrigin names the page's origin. In dev this is why the browser connects through the same-origin Vite proxy rather than straight to :8443.

What the backend does with it

Nothing over HTTP for room lifecycle. There is no provisioning call: the room exists because the token names it and auto-subgroups allows it. The join path makes zero requests to the SFU.

Join tokens are signed locallybackend/src/app/sfu/token.py. Galene checks the signature against the group's authKeys, parses each aud entry as a URL and matches its path against /group/<name>/, then takes sub as the username and permissions as the permission list.

Presence and control ride an operator connectionbackend/src/app/sfu/operator.py. The backend joins each active room as a client with the op permission, opened on first join and released when the last participant leaves. That connection is where participant_joined / participant_left come from, and where kick is issued.

The operator is visible

group.AddClient pushes user add to every client unconditionally; upstream has no hidden-client mode. The bot therefore appears in everybody's roster, and the frontend filters it using the operator_id on the join ticket.

What upstream gives you that Beep doesn't use

  • /galene-api/v0/ — configuration and inspection (groups, users, tokens, .stats). Beep reads none of it today.
  • The built-in web UI at /group/<name>/. Beep ships its own frontend, but upstream's is served from vendor/galene/static/ and is handy for debugging against a bare SFU.
  • WHIP ingestion, galenectl, the stateful-token store, the built-in TURN server (-turn "" disables it in dev).

Known limits

  • Per-room configuration is uniform. Every room under meetings/ inherits the same description — same max-clients, same recording policy. Per-user permissions still vary freely, because they come from the token. Per-room settings would need real files, which means writableGroups and the /galene-api/v0/ CRUD surface.
  • Recording has no completion signal. Nothing tells the backend the diskwriter finished; polling the recordings directory is the fallback. Recording is not wired up in Beep today.