Jitsi: инвентарь изменений в thirdparty/jitsi
Полный перечень того, что PIN меняет/добавляет поверх саморазмещённого Jitsi, чтобы медиа-стек работал с
контрактом messenger (JWT входа, authoritative-admission, presence-вебхуки, авто-запись → file storage).
Для каждого пункта — файл-источник в thirdparty/jitsi/*, характер изменения (конфиг / новый файл /
env) и куда он ложится по гиду. Мета-репозиторий Jitsi поставляет Nomad-IaC как референс конфигурации;
PIN переносит ключи в собственные Helm/k8s-манифесты (jitsi-deployment.md) — здесь
перечислены сами параметры, а не способ доставки.
Характер большинства правок — конфигурация, не форк. Открытые prosody-плагины и jicofo включаются и параметризуются штатными опциями; собственного кода PIN добавляет минимум: один prosody-модуль присутствия и один jibri finalize-скрипт. Всё остальное — значения опций и env.
prosody (XMPP: комнаты, presence, JWT, admission)
Файлы плагинов — thirdparty/jitsi/jitsi-meet/resources/prosody-plugins/; vhost-конфиг PIN авторит сам.
| Что | Файл-источник | Характер | Значение / назначение |
|---|---|---|---|
| Токен-аутентификация vhost | mod_auth_token.lua, token/util.lib.lua | конфиг | authentication = "token"; app_id = pin_messenger, app_secret из vault (env JWT_APP_ID/JWT_APP_SECRET) — prosody проверяет HS256-подпись JWT входа (jitsi-jwt.md) |
| Проверка привязки токена к комнате | mod_token_verification.lua | конфиг | modules_enabled += "token_verification" — room-claim обязан совпадать с именем комнаты (токен conf-A не пускает в conf-B) |
| Роль модератора из claim-а | mod_token_affiliation.lua | конфиг | modules_enabled += "token_affiliation" — context.user.moderator == true / affiliation == owner → affiliation owner (роль moderator: запись/удаление/завершение, INV-CONF-6) |
| Authoritative admission | mod_reservations.lua | конфиг | modules_enabled += "reservations"; reservations_api_prefix = <messenger>/api/internal/jitsi; reservations_api_headers (HMAC/Bearer); reservations_api_timeout (20с); reservations_api_retry_count/_delay — комната создаётся только после подтверждения messenger (jitsi-rooms.md) |
| Presence-вебхуки (ростер) | новый mod_pin_muc_presence.lua | новый файл | кастомный модуль: hooks muc-occupant-joined/muc-occupant-left → POST /internal/jitsi/events (авторитет состава, INV-CPART-5). Обоснование vs prosody-egress/отсутствующий muc_webhooks — jitsi-rooms.md |
| Домен MUC-маппинга | mod_muc_domain_mapper.lua | конфиг | muc_mapper_domain_base = <MediaDomain> (домен развёртывания = MediaRoom.Domain) |
Отключаются неиспользуемые модули открытой поставки (breakout-rooms, lobby, av-moderation, polls и пр.) — PIN включает только token-auth, token-verification, token-affiliation, reservations, muc-mapper и собственный presence-модуль.
jicofo (focus: конференции, brewery, оркестрация jibri)
Файл — thirdparty/jitsi/jicofo/jicofo-selector/src/main/resources/reference.conf (значения PIN задаёт
override-конфигом/env, не правкой reference).
| Что | Ключ (reference.conf) | Характер | Значение для PIN |
|---|---|---|---|
| Brewery jibri | jibri.brewery-jid (закомментирован, строка ~305) | конфиг | jibribrewery@internal.auth.<domain> — пул рекордеров для авто-записи (recording-pipeline.md) |
| Проверка модератора | conference.enable-moderator-checks (строка ~219) | конфиг | true — запись/привилегии только модератору (INV-CONF-6) |
| Транскрипция (будущее) | transcription.url-template (закомментирован, строка ~445) | конфиг (оставить off) | не задаём в MVP; шов для live-STT (transcription-roadmap.md) |
JVB (jitsi-videobridge) — без правок кода: публичный UDP 10000, octo.enabled = false (один регион,
ADR-0056), параметризация через env (jitsi-deployment.md).
jibri (рекордер → финализация)
Env-референс — thirdparty/jitsi/infra-provisioning/nomad/jibri.hcl; finalize-скрипт PIN добавляет в образ.
| Что | Источник | Характер | Значение для PIN |
|---|---|---|---|
| Finalize-скрипт | jibri.hcl → JIBRI_FINALIZE_RECORDING_SCRIPT_PATH (был /usr/bin/jitsi_uploader.sh) | env + новый файл | = /usr/bin/pin_finalize.sh — новый скрипт (замена jitsi_uploader.sh): читает metadata.json, стримит multipart в messenger, при 2xx удаляет локальную директорию (контракт — recording-pipeline.md) |
| Директория записи | jibri.hcl → JIBRI_RECORDING_DIR | env | /mnt/recordings (persistent volume) |
| Включение записи | JIBRI_ENABLE_RECORDING / ENABLE_RECORDING | env | true при RECORDINGS_ENABLED |
| Авто-shutdown простоя | jibri.hcl → JIBRI_USAGE_TIMEOUT | env | 61 minutes |
Обогащение метаданных записи (initiator, conference_details.session_id, проброс pin.conferenceId/
pin.tenantId) делает штатный mod_jibri_session.lua — без изменений, только используется
(recording-pipeline.md).
coturn (TURN/STUN)
Референс — thirdparty/jitsi/infra-provisioning/nomad/coturn.hcl.
| Что | Ключ | Характер | Значение для PIN |
|---|---|---|---|
| Секретная TURN-аутентификация | use-auth-secret, static-auth-secret | конфиг | общий секрет из vault; messenger минтит короткоживущие TURN-креды (MediaIceServerEntry.Credential) тем же секретом (jitsi-jwt.md) |
| Realm | realm | конфиг | домен развёртывания медиа |
| Транспорт | TLS на 443 | конфиг | TURN over TLS (обход строгого NAT/файрвола) |
lib-jitsi-meet (нативный клиент)
Не серверный компонент — вендорится из thirdparty/jitsi/lib-jitsi-meet (в npm не публикуется), собирается
в монорепо фронта (jitsi-client.md). Правок кода библиотеки нет — только клиентский конфиг:
| Кноб | Значение | Зачем |
|---|---|---|
disableThirdPartyRequests | true | никаких внешних вызовов (gravatar/аналитика) — приватность + self-host |
p2p.enabled | true (1:1) | медиа напрямую между двумя; escalation на JVB при 3+ |
serviceUrl / hosts.domain | из SignalingUrl/MediaDomain | endpoint prosody из MediaJoinModel |
Новые артефакты PIN (не из открытой поставки)
| Артефакт | Где живёт | Роль |
|---|---|---|
mod_pin_muc_presence.lua | prosody-плагины развёртывания | presence-вебхуки join/leave → messenger (авторитет ростера) |
pin_finalize.sh | образ jibri (/usr/bin/) | финализация записи: metadata.json → multipart-stream в messenger |
| Helm/k8s-манифесты | инфра-репозиторий PIN | развёртывание prosody/jicofo/JVB/jibri/coturn (jitsi-manifests.md) |
pin_finalize.sh (полный текст)
Заменяет штатный jitsi_uploader.sh (env JIBRI_FINALIZE_RECORDING_SCRIPT_PATH). jibri вызывает его с
директорией завершённой записи ($1). Скрипт читает metadata.json, стримит медиа multipart в
messenger (память не буферизуется — curl -F читает файл с диска), при 2xx удаляет директорию, иначе
выходит с ненулевым кодом (jibri/оператор ретраит). Контракт — recording-pipeline.md,
эндпоинт — ../api/internal/.
#!/usr/bin/env bash
# pin_finalize.sh — финализация записи jibri → ingest в messenger.
# Вызов: pin_finalize.sh <recording_dir>
# Заменяет /usr/bin/jitsi_uploader.sh (JIBRI_FINALIZE_RECORDING_SCRIPT_PATH).
set -euo pipefail
REC_DIR="${1:?usage: pin_finalize.sh <recording_dir>}"
META="${REC_DIR}/metadata.json"
# messenger internal API + секрет вебхука (смонтированы в под jibri, см. jitsi-manifests.md)
MSG_BASE="${MESSENGER_INTERNAL_URL:?MESSENGER_INTERNAL_URL not set}"
WEBHOOK_SECRET="${JITSI_WEBHOOK_TOKEN:?JITSI_WEBHOOK_TOKEN not set}"
[ -f "${META}" ] || { echo "pin_finalize: metadata.json missing in ${REC_DIR}" >&2; exit 1; }
# sessionId = наш conferenceId/callSessionId (проброшен клиентом в app_data.pin.*);
# session_id из conference_details — запасной ключ (= meetingId).
SESSION_ID="$(jq -r '.pin.conferenceId // .conference_details.session_id // empty' "${META}")"
TENANT_ID="$(jq -r '.pin.tenantId // empty' "${META}")"
[ -n "${SESSION_ID}" ] || { echo "pin_finalize: no session id in metadata.json" >&2; exit 1; }
# берём единственный медиа-файл записи (mp4/webm/mkv)
MEDIA_FILE="$(find "${REC_DIR}" -maxdepth 1 -type f \
\( -name '*.mp4' -o -name '*.webm' -o -name '*.mkv' \) | head -n1)"
[ -n "${MEDIA_FILE}" ] || { echo "pin_finalize: no media file in ${REC_DIR}" >&2; exit 1; }
# HMAC-подпись пути (replay-window: timestamp в подписи + заголовке), fail-closed на стороне messenger
TS="$(date -u +%s)"
SIG="$(printf '%s.%s' "${TS}" "${SESSION_ID}" \
| openssl dgst -sha256 -hmac "${WEBHOOK_SECRET}" -binary | xxd -p -c 256)"
URL="${MSG_BASE}/api/messenger/internal/call-recordings/${SESSION_ID}/ingest"
# streamed multipart: файл читается с диска (memory-bounded), метаданные — отдельным полем
HTTP_CODE="$(curl -sS -o /dev/null -w '%{http_code}' -X POST "${URL}" \
-H "X-Pin-Timestamp: ${TS}" \
-H "X-Pin-Signature: ${SIG}" \
-H "Authorization: Bearer ${WEBHOOK_SECRET}" \
${TENANT_ID:+-H "X-Pin-Tenant: ${TENANT_ID}"} \
-F "metadata=@${META};type=application/json" \
-F "file=@${MEDIA_FILE};type=video/mp4")"
if [[ "${HTTP_CODE}" =~ ^2 ]]; then
echo "pin_finalize: ingest ok (${HTTP_CODE}) session=${SESSION_ID}; cleaning ${REC_DIR}"
rm -rf "${REC_DIR}"
exit 0
else
echo "pin_finalize: ingest failed (${HTTP_CODE}) session=${SESSION_ID}; keeping ${REC_DIR}" >&2
exit 1
fi
curl -F стримит файл с диска, не читая его целиком в память; байты далее проксирует messenger в file
storage (recording-pipeline.md). На не-2xx директория не удаляется — запись
остаётся вне Available, оператор/повторный finalize доводит выгрузку.
mod_pin_muc_presence.lua (набросок)
Собственный prosody-модуль (главный MUC-компонент): на muc-occupant-joined/muc-occupant-left шлёт
неблокирующий HTTP-POST в messenger — авторитет состава конференции (INV-CPART-5,
jitsi-rooms.md). Пропускает jibri/transcriber (по образцу
mod_muc_cleanup_backend_services.lua), async-HTTP — по образцу mod_reservations.lua.
-- mod_pin_muc_presence.lua — presence-вебхуки MUC → messenger.
-- Грузится под главным MUC-компонентом (conference.<domain>).
local http = require 'net.http';
local json = require 'cjson.safe';
local util = module:require 'util';
local is_jibri = util.is_jibri;
local is_transcriber = util.is_transcriber;
local jid = require 'util.jid';
local api_url = module:get_option_string('pin_muc_presence_api_url'); -- .../internal/jitsi/events
local api_token = module:get_option_string('pin_muc_presence_api_token', os.getenv('JITSI_WEBHOOK_TOKEN'));
-- неблокирующий POST (fire-and-forget с логом ошибки), по образцу async_http_request в mod_reservations
local function post_event(payload)
local body = json.encode(payload);
http.request(api_url, {
method = 'POST';
body = body;
headers = {
['Content-Type'] = 'application/json';
['Authorization'] = 'Bearer '..api_token; -- + HMAC на стороне messenger (replay-window)
['X-Pin-Timestamp'] = tostring(os.time());
};
}, function (_, code)
if not (code >= 200 and code < 300) then
module:log('warn', 'pin_muc_presence POST %s failed: %s', payload.event, code);
end
end);
end
local function on_presence(evt_kind)
return function (event)
local room, occupant = event.room, event.occupant;
if is_jibri(occupant.jid) or is_transcriber(occupant.jid) then
return; -- backend-сервисы не участники ростера
end
local ctx = event.origin and event.origin.jitsi_meet_context_user;
post_event({
room = jid.node(room.jid); -- call-{id} / conf-{id}
occupantJid = tostring(occupant.jid);
nick = occupant.nick and jid.resource(occupant.nick) or nil;
userId = ctx and ctx.id or nil; -- из JWT context.user.id
role = occupant.role; -- moderator/participant
event = evt_kind; -- 'Joined' | 'Left'
occurredAt = os.date('!%Y-%m-%dT%H:%M:%SZ');
});
end
end
module:hook('muc-occupant-joined', on_presence('Joined'), -100);
module:hook('muc-occupant-left', on_presence('Left'), -100);
Приоритет -100 (последними в цепочке) — как в mod_muc_cleanup_backend_services.lua. messenger
маппит room → сессия, event → CallParticipant.State; при расхождении с клиентскими join/leave
побеждает серверное событие.
Сводный список изменений (репо-пути)
Полный перечень «что и где» одним взглядом — конкретные пути в thirdparty/jitsi/* и характер правки.
| # | Компонент | Файл / ключ (репо-путь) | Характер | Значение для PIN |
|---|---|---|---|---|
| 1 | prosody | jitsi-meet/resources/prosody-plugins/mod_auth_token.lua + token/util.lib.lua | конфиг vhost | authentication="token", app_id=pin_messenger, app_secret (vault) |
| 2 | prosody | jitsi-meet/resources/prosody-plugins/mod_token_verification.lua | modules_enabled += | room-claim == имя комнаты |
| 3 | prosody | jitsi-meet/resources/prosody-plugins/mod_token_affiliation.lua | modules_enabled += | context.user.moderator/affiliation=owner → moderator (стр. 28–45) |
| 4 | prosody | jitsi-meet/resources/prosody-plugins/mod_reservations.lua | modules_enabled += + опции | reservations_api_prefix/_headers/_timeout; POST/DELETE /conference (стр. 371–393, 617–643) |
| 5 | prosody | jitsi-meet/resources/prosody-plugins/mod_jibri_session.lua | modules_enabled += | обогащение app_data записи — без правок (стр. 25–91) |
| 6 | prosody | jitsi-meet/resources/prosody-plugins/mod_muc_domain_mapper.lua | конфиг | muc_mapper_domain_base = <domain> |
| 7 | prosody | новый mod_pin_muc_presence.lua (образец: mod_muc_cleanup_backend_services.lua стр. 30–74, async — mod_reservations.lua стр. 161–196) | новый файл | presence join/leave → POST /internal/jitsi/events |
| 8 | prosody | main-vhost .cfg.lua (авторит PIN) | новый файл | набор modules_enabled + reservation/presence-URL (jitsi-deployment.md) |
| 9 | jicofo | jicofo/jicofo-selector/src/main/resources/reference.conf:305 jibri.brewery-jid | override | jibribrewery@internal.auth.<domain> |
| 10 | jicofo | jicofo/.../reference.conf:219 conference.enable-moderator-checks | override | true |
| 11 | jicofo | jicofo/.../reference.conf:445 transcription.url-template | оставить off | не задаём в MVP |
| 12 | JVB | env (octo.enabled=false, DOCKER_HOST_ADDRESS) | конфиг | без правок кода; публичный UDP 10000 |
| 13 | jibri | infra-provisioning/nomad/jibri.hcl:213 JIBRI_FINALIZE_RECORDING_SCRIPT_PATH | env + новый файл | = /usr/bin/pin_finalize.sh |
| 14 | jibri | infra-provisioning/nomad/jibri.hcl:214 JIBRI_RECORDING_DIR | env | /mnt/recordings |
| 15 | jibri | infra-provisioning/nomad/jibri.hcl:213–219 ENABLE_RECORDING/JIBRI_USAGE_TIMEOUT | env | true / 61 minutes |
| 16 | jibri | новый /usr/bin/pin_finalize.sh (в образе) | новый файл | multipart-stream ingest → messenger |
| 17 | coturn | infra-provisioning/nomad/coturn.hcl:74,108–110 use-auth-secret/static-auth-secret/realm | конфиг | общий TURN-секрет (vault), realm <domain>, TLS 443 |
| 18 | lib-jitsi-meet | lib-jitsi-meet (вендор) — клиентский конфиг | конфиг | disableThirdPartyRequests, p2p.enabled, serviceUrl (jitsi-client.md) |
Собственного кода PIN — ровно два артефакта (строки 7 и 16); всё остальное — значения опций/env поверх открытой поставки.
Не трогаем (референс на будущее)
jigasi (SIP), multitrack-recorder (nomad/multitrack-recorder.hcl), transcriber
(nomad/transcriber.hcl), веб-SPA jitsi-meet, Octo/мультирегион — вне MVP; относятся к
transcription-roadmap.md и right-sizing (jitsi-deployment.md).
Обратные ссылки
Автоссылки: где используется этот документ.
- calls (8): Медиа-звонки и запись на Jitsi — интеграционный гид (messenger), Медиа-звонки на Jitsi: план поставки (playbook аналитик → разработчик), Jitsi-звонки: план верификации и приёмки, Jitsi: именование комнат, admission и presence, Jitsi: минтинг JWT входа в .NET, Jitsi: сквозной пайплайн авто-записи, Jitsi: k8s-топология и взаимодействие с бэкендом, Jitsi: k8s/Helm-манифесты (SKETCH)
Связанные документы
- Гид: README.md; развёртывание/env: jitsi-deployment.md; JWT: jitsi-jwt.md; комнаты/presence: jitsi-rooms.md; клиент: jitsi-client.md; запись: recording-pipeline.md; роадмап STT: transcription-roadmap.md.
- Вебхуки (контракты): ../api/internal/; дескриптор входа: MediaJoinModel.
- Источники:
thirdparty/jitsi/jitsi-meet/resources/prosody-plugins/{mod_auth_token,mod_token_verification,mod_token_affiliation,mod_reservations,mod_jibri_session,mod_muc_domain_mapper,mod_muc_cleanup_backend_services}.lua,token/util.lib.lua;thirdparty/jitsi/jicofo/jicofo-selector/src/main/resources/reference.conf;thirdparty/jitsi/infra-provisioning/nomad/{jibri,coturn}.hcl;thirdparty/jitsi/lib-jitsi-meet. - Решение: ADR-0062 медиа-стек и запись на Jitsi.