README.md
meet-kit: keeping an open room on a Jitsi Meet answerable
Three small programs for whoever runs a Jitsi Meet with a room that anyone may walk into. They were written for common-lisp.net and are not particular to it: every setting is in the environment, and a second site uses them unchanged.
- The watch (
watch/meet-watch.sh) tells the people answerable for the Meet what it is used for, without anyone sitting in it: a notification when a room opens and closes, when people come into a room that is kept open, when the Meet stops answering, and when docker-jitsi-meet has a newer release than the Meet runs. - The keeper (
keeper/meet-keeper.js) holds one room open day and night on a Meet that otherwise asks for a login, and can keep a small still picture of the room at intervals while people are in it. - Recording on request: anyone in the kept room may type
!recordin the chat, and!stop. The keeper starts the Meet's own recorder (Jibri), which shows everyone the recording mark and says so aloud; it tells the room what is happening and what it costs; and when the recording ends, the recorder's hook (recorder/finalize.sh) hands the file to the application that owns the room, whose address for it the keeper posts in the chat. What a recording costs, how long it is kept and who may fetch it are the application's business.
The watch and the keeper's pictures take no sound; only a recording does, and it cannot start unseen. None of them learns a name: the Meet knows the people in a room by an opaque number that means nothing outside that visit.
What you owe the people in the room
The keeper is a participant, and everyone in the room sees it. If it keeps pictures, its name in the room says so, and the page that leads people into the room should say what is kept, why, who can see it and for how long, before the pictures are switched on. Keep them no longer than it takes to judge a report of abuse; the default is a day.
Running them
compose/docker-compose.meet-kit.yml runs the watch and the keeper as
two services beside a Meet on the same machine; lay it over your own
compose file and set, in that project's .env:
| Setting | What it is |
| --- | --- |
| MEET_NETWORK | The Meet's Docker network, from docker network ls. |
| MEET_URL | The Meet's public address. |
| MEET_VERSION | The docker-jitsi-meet release it runs (stable-11248), for the daily "is there a newer one?". |
| KEEPER_ROOM | The room kept open. Empty: no keeper, and the watch counts no room as kept. |
| KEEPER_USER, KEEPER_PASSWORD | The keeper's account on the Meet (internal authentication). |
| KEEPER_JWT | Or a token, for a Meet that seats its moderators by token. |
| KEEPER_GUEST | Or 1, for a Meet whose rooms open without a login. |
| KEEPER_SHOTS | A directory in the keeper's container (/shots is a volume) to keep pictures in. Empty: none. |
| KEEPER_SHOT_URL, KEEPER_SHOT_TOKEN | Or an address each picture is POSTed to, with a bearer token, for an application on another machine. The room and the picture's name are in the query. |
| KEEPER_SHOT_EVERY, KEEPER_SHOT_KEEP_HOURS | Seconds between pictures (60); hours a file is kept (24). |
| WATCH_NOTIFY_URL, WATCH_NOTIFY_TOKEN | An ntfy topic's address and a token that may write to it. Without them the watch only logs. |
| WATCH_ALLOWED_ROOMS | Rooms the Meet exists for, space-separated: logged, never pushed. When set, any other room's opening is pushed as a warning. |
| WATCH_REPORT_URL, WATCH_REPORT_TOKEN | An address every event line is POSTed to as it is logged, for an application that shows the room's numbers. |
| WATCH_QUIET | Seconds between two "people came in" pushes for a kept room (600). |
The Meet itself needs JICOFO_ENABLE_REST=1 in its .env for the
watch, and an account for the keeper:
$ docker compose exec prosody sh -c 'prosodyctl --config \
$(ls /run/prosody/config/prosody.cfg.lua /config/prosody.cfg.lua 2>/dev/null | head -1) \
register <name> meet.jitsi <password>'
Both scripts say at the top what each line of their logs means.
Recording
The Meet needs its recorder: docker-jitsi-meet's jibri.yml beside its
docker-compose.yml (named in the Meet's COMPOSE_FILE),
ENABLE_RECORDING=1, and the directories <CONFIG>/jibri and
<CONFIG>/storage/jibri owned by uid 1000. One Jibri records one room
at a time, and takes two to four gigabytes of memory and a processor
core while it does.
The hook: copy recorder/finalize.sh into <CONFIG>/jibri/, beside a
meet-kit.env that says where the application is,
RECORD_REPORT_URL=https://example.org/meet-record/finalize
RECORD_REPORT_TOKEN=<a long random token>
and in the Meet's .env
JIBRI_FINALIZE_RECORDING_SCRIPT_PATH=/config/finalize.sh. The
application mounts <CONFIG>/storage/jibri/recordings.
An application on another machine cannot mount it. Add to
meet-kit.env
RECORD_UPLOAD_URL=https://example.org/meet-record/part
RECORD_UPLOAD_DELETE=1
and the hook first sends the video there in parts of
RECORD_UPLOAD_PART_MB megabytes (8 by default, under any proxy's limit
on a request): POST <url>?session=...&file=...&part=<n>, the part's
bytes as the body, the same bearer token, each part tried three times.
The application joins the parts in order when the report comes. With
RECORD_UPLOAD_DELETE=1 the Meet's copy is deleted once the report is
answered with 200.
The keeper: KEEPER_RECORD_URL (the application's address, without
the last word: https://example.org/meet-record) and
KEEPER_RECORD_TOKEN, the same token. Without them the keeper ignores
!record.
What the application answers (all with the bearer token):
| Request | Answer |
| --- | --- |
| POST <url>/start {"room": ...} | {"ok": true, "say": "what it costs"} to allow the first half hour, {"ok": false, "say": "why not"} to refuse it. The keeper says say in the chat. |
| POST <url>/tick {"room": ...} | The same, before each further half hour. |
| POST <url>/finalize {"session", "room", "file", "bytes", "seconds"} | From the hook. The application now owns the file. |
| GET <url>/link?room=...&since=<seconds since 1970> | {"url": "...", "say": "..."} once there is an address for the newest recording of the room, which the keeper posts. |
A recording ends at !stop, when the room empties, when a half hour
is refused, or after KEEPER_RECORD_MAX_MINUTES (120). A moderator
can also record with the Meet's own button, in any room; the hook
reports those the same way, and nobody was asked beforehand what they
cost.
What an application does with the pictures
The keeper names a picture <UTC time>-<people>p.jpg
(20261003T143029Z-2p.jpg), under a directory named for the room. The
application shows them to the people answerable for the room and to
nobody else, and serves a picture only after checking the room's and
the picture's names against those shapes. common-lisp.net's is
/admin/meet-shots in
its site's source, in
Common Lisp.
Licence
GNU Affero General Public License, version 3 or later (LICENSE).