The SFU (upstream Galene)¶
Beep's SFU is unmodified upstream Galene, pinned to a specific commit. No patches, no fork.
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-subgroupsmakes everymeetings/<slug>a joinable room the moment somebody joins it. Galene's description lookup walks up the path —meetings/room-abcfalls back tomeetings.json— and the subgroup inherits the parent's whole description,authKeysincluded. No per-room file is ever written, so the SFU needs no writable directory at runtime.authKeysis the key the backend signs join tokens with. Sincesubandpermissionscome 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:
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 locally — backend/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 connection —
backend/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 fromvendor/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 — samemax-clients, same recording policy. Per-user permissions still vary freely, because they come from the token. Per-room settings would need real files, which meanswritableGroupsand 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.