716 lines
59 KiB
Markdown
716 lines
59 KiB
Markdown
# Изменения
|
||
|
||
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
||
проект придерживается [семантического версионирования](https://semver.org/lang/ru/).
|
||
|
||
## [0.0.28] — 2026-08-04
|
||
|
||
Очередь поднятых рук видна всем участникам + отключаемый модуль.
|
||
|
||
### Добавлено
|
||
- Очередь поднятых рук больше не спрятана от обычных участников — её
|
||
видит любой, кто в конференции, не только организатор. Опустить чужую
|
||
руку по-прежнему может только организатор: сервер это уже проверял
|
||
(`api/chat.py`, задача B1), правки — только на фронте, кнопка
|
||
«Опустить» показывается у записи, если это своя рука либо пользователь
|
||
сам организатор.
|
||
- «Поднятие руки» (кнопка «Рука» + очередь целиком) — отключаемый модуль
|
||
в админке (`instance_settings.hand_queue`, дефолт `enabled=true` —
|
||
поведение существующих инсталляций не меняется). Настройка едет
|
||
участнику в `JoinOut` (как `chat_enabled`) до входа в комнату.
|
||
Выключенный модуль гасит кнопки на фронте и отклоняет
|
||
`raise_hand`/`lower_hand` на сервере кодом `hand_queue_disabled` —
|
||
вторая линия защиты для клиента со старым `JoinOut` на руках.
|
||
Переключение применяется со следующего входа в комнату (та же
|
||
застылость на время жизни соединения, что и у `chat_enabled`).
|
||
|
||
## [0.0.27] — 2026-08-04
|
||
|
||
Разрыв связи больше не выбрасывает участника из конференции.
|
||
|
||
### Исправлено
|
||
- Телефон с погасшим экраном выпадал из конференции, а при возвращении
|
||
мог оказаться в лобби вместо комнаты. Причина установлена по логам
|
||
прода: Chrome срезает ресурсы фоновой вкладке, телефон перестаёт
|
||
отвечать по ICE, и LiveKit закрывает участника через 5 с после потери
|
||
соединения (замерено: 37 с после блокировки экрана, 23 с после
|
||
сворачивания браузера — то есть достаточно просто убрать вкладку в
|
||
фон, гасить экран не обязательно). Восстановить сессию после этого
|
||
нельзя — участника на сервере уже нет; нужен полный повторный вход,
|
||
и `livekit-client` пытается сделать его сам, но его бюджет повторов
|
||
(10 попыток за ~44 с) в фоновой вкладке успевает сгореть. Тогда
|
||
приходило событие `Disconnected`, и страница комнаты уводила
|
||
пользователя в лобби.
|
||
|
||
Теперь непреднамеренный разрыв не уводит со страницы: сбрасывается
|
||
состояние входа, и работает уже имевшийся путь авто-перезахода —
|
||
резолв конференции, свежий токен, вход заново. Намеренный выход
|
||
отличается по нажатию кнопки «Выйти», а не по коду причины: причину
|
||
`CLIENT_INITIATED` присылает и кнопка, и сам `livekit-client`, который
|
||
при заморозке вкладки (событие `freeze`) вызывает `disconnect()`
|
||
самостоятельно. Разрывы, после которых возвращаться нельзя (участника
|
||
выгнал организатор, конференция закрыта, вход той же личностью с
|
||
другого устройства), уводят в лобби как раньше. От цикла «вошёл —
|
||
сразу выбросило» защищает лимит в 5 перезаходов подряд; соединение,
|
||
прожившее дольше 30 с, счётчик обнуляет.
|
||
|
||
## [0.0.26] — 2026-08-04
|
||
|
||
Мобильная комната: свайп шторки настроек и полноэкранный режим.
|
||
|
||
### Исправлено
|
||
- Шторка «Настройки» на мобильном не закрывалась свайпом вниз почти никогда:
|
||
обработчики висели только на ручке-волоске (`.room-sheet-handle`,
|
||
40×4px) — палец в неё практически невозможно попасть, и палец почти
|
||
всегда приземлялся на панель, где обработчиков не было вовсе. Теперь
|
||
свайп закрывает шторку при жесте по любому месту панели, но только
|
||
когда её содержимое проскроллено в самый верх (`scrollTop === 0` на
|
||
начало жеста) — иначе свайп вниз листает список устройств, как и должен
|
||
стандартный bottom sheet.
|
||
|
||
### Добавлено
|
||
- Полноэкранный режим комнаты — на мобильном кнопка «Экран» в тулбаре (как
|
||
на десктопе), при активации топбар и тулбар уходят из потока и лежат
|
||
оверлеем поверх сцены: показываются по тапу/клику по сцене вне элементов
|
||
управления и сами прячутся через несколько секунд бездействия. На
|
||
десктопе — та же логика (топбар/тулбар тоже прячутся в полноэкранном
|
||
режиме), дополнительный способ вернуть их — навести мышь в нижнюю полосу
|
||
экрана.
|
||
|
||
### Изменено
|
||
- Кнопка настроек устройств в тулбаре на мобильном подписана «Настройки»
|
||
вместо «Устройства».
|
||
- Раскладка кнопок мобильного тулбара, когда они не помещаются в один ряд
|
||
(7 и больше — с полноэкранным режимом и «Очередью» у организатора это
|
||
обычный случай), стала равномерной сеткой на 4 колонки (7 → 4+3,
|
||
8 → 4+4) вместо переноса «как получится» через `flex-wrap`.
|
||
- `deploy/render-templates.sh` умеет читать значения из файла, заданного
|
||
переменной `ENV_FILE`, а не только из корневого `.env` (см. коммит
|
||
`3847476`, вошёл в этот релиз) — для локальных стендов, где корневой
|
||
`.env` указывает на боевые адреса.
|
||
|
||
## [0.0.25] — 2026-08-03
|
||
|
||
Два дефекта мини-окна конференции (Document PiP), оба видны только в Chrome.
|
||
|
||
### Исправлено
|
||
- В Chrome пропадал звук других участников после возврата сцены из
|
||
мини-окна в основное. `RoomAudioRenderer` жил внутри `RoomStage` и
|
||
рендерился в обеих её ветках, поэтому скрытые `<audio>` с чужими
|
||
аудиотреками физически переезжали в документ PiP-окна и обратно.
|
||
После такого переезда Chrome теряет аудиовыход у remote-трека: пакеты
|
||
продолжают приходить (`packetsReceived` растёт), а
|
||
`totalSamplesDuration` и `totalAudioEnergy` замирают, и трек молчит
|
||
даже в свежесозданном `<audio>` со свежим `MediaStream`. Тот же цикл
|
||
detach/attach в пределах одного документа безвреден — дело именно в
|
||
переезде между документами. Рендерер вынесен в `RoomPage`: один
|
||
экземпляр, всегда в основном документе, в PiP-окне аудиоэлементов нет
|
||
вовсе. В Safari бага не было — там Document PiP не используется
|
||
(video-фолбэк), сцена из основного окна не уезжает.
|
||
- В мини-окне не действовало закрепление участника, а демонстрация
|
||
экрана слетала на говорящего от любой чужой реплики. Мини-плеер
|
||
намеренно ходил с упрощёнными правилами выбора фокуса (без
|
||
`holdScreenShare`, без приоритета говорящего с камерой, без
|
||
антидребезга) и с собственным локальным состоянием закрепления.
|
||
Теперь `pickStageFocus` получает одинаковые правила в обоих вариантах
|
||
сцены, а закрепление переезжает между окнами тем же мостиком через
|
||
`RoomPage`, что и фокус. Сознательно оставлено одно отличие —
|
||
фолбэк «показать себя» (`localKey`) только у мини-плеера: это защита
|
||
от дефекта 0.0.11, когда мини-окно открывалось на самом пользователе.
|
||
- Закрепление сбрасывалось при каждом монтировании сцены: на первом
|
||
рендере `useTracks` отдаёт пустой набор треков, и правило «закреплённый
|
||
вышел из комнаты» принимало это за уход участника.
|
||
|
||
### Изменено
|
||
- Кнопка-булавка на плитке подписана «Закрепить» вместо «Закрепить в
|
||
основном окне» — закрепление больше не ограничено основным окном.
|
||
|
||
## [0.0.24] — 2026-08-03
|
||
|
||
Три артефакта вёрстки, вылезающие за границы блоков (эмодзи-поповер чата,
|
||
заголовок брендовой панели, чат в Firefox) + аудит похожих мест.
|
||
|
||
### Исправлено
|
||
- Заголовок «Ваша инфраструктура» на странице входа вылезал за край
|
||
брендовой панели на широком окне с узкой панелью (`.layout` — flex
|
||
42/58) — старый фикс уменьшал кегль только по ширине ОКНА (`@media`),
|
||
а не панели. Кегль `.brand-headline` теперь считается через container
|
||
query (`container-type: inline-size` + `cqw`) — тот же приём, что у
|
||
аватара участника в комнате. Тем же механизмом обрезались плашки
|
||
статистики («AI-саммари») — `.brand-stats` теперь переносит их на
|
||
мобильном и десктопе одинаково.
|
||
- Эмодзи-поповер в чате комнаты: последняя (5-я) колонка вылезала за
|
||
правый край поповера. Причина — гонка CSS-специфичности: правило
|
||
круглой кнопки «Отправить» (`.chat-input-row button`, 42×42px)
|
||
продолжало красить размер и кнопкам эмодзи внутри поповера (та же
|
||
гонка чинилась для цвета в 0.0.19, но не для размера). Добавлены явные
|
||
`width`/`height: auto` нужной специфичности + `max-width` на попап как
|
||
общая страховка.
|
||
- Чат комнаты вылезал за границы панели в Firefox: `.chat-messages`
|
||
(flex-колонка) без `min-height: 0` не сжималась в Firefox, `textarea`
|
||
поля ввода без `min-width: 0` упиралась в автоматическую минимальную
|
||
ширину (Firefox считает её от атрибута `cols`, жёстче Chrome/Safari).
|
||
|
||
## [0.0.23] — 2026-08-02
|
||
|
||
Документация по сайзингу под медиа-нагрузку + видимость TURN-аллокаций в логах.
|
||
|
||
### Добавлено
|
||
- `docs/deploy/hardware-sizing.md` — таблица «профиль нагрузки → CPU/RAM/
|
||
полоса» для медиа (видеоконференции), на реальных боевых замерах
|
||
28.07 и 31.07.2026, с формулой для расчёта под свой сценарий.
|
||
|
||
### Исправлено
|
||
- coturn: включено verbose-логирование — дефолтный уровень не писал
|
||
построчно `ALLOCATE`/`CreatePermission`/`Refresh` даже при рабочем
|
||
relay-соединении (найдено на релизе 0.0.22 — звонок через TURN работал,
|
||
а `grep -ci allocate` по логам coturn показывал 0).
|
||
|
||
## [0.0.22] — 2026-08-02
|
||
|
||
TURN over TLS (5349) — для клиентов из сетей, где наружу открыт только 443.
|
||
|
||
### Добавлено
|
||
- coturn слушает TLS на `5349` (сертификат Let's Encrypt, обновляется
|
||
автоматически). Включается одним ключом `TURN_TLS_HOST` в `.env` (домен
|
||
сертификата, НЕ IP) — пусто оставляет прежнее поведение без единого следа
|
||
в рендеренных конфигах.
|
||
- LiveKit объявляет клиентам запись `protocol: tls` в `rtc.turn_servers`
|
||
(последней в списке, как самый дорогой фолбэк) — без анонса включённый
|
||
TLS был бы бесполезен: именно так уже случалось с обычным TURN до 0.0.14
|
||
(сервис работал healthy, но не обслужил ни одной аллокации).
|
||
- Новый init-контейнер `coturn-certs-init`: копирует fullchain/privkey из
|
||
`/etc/letsencrypt` в отдельный volume под правами `644` — coturn
|
||
(`nobody:nogroup`, без root-фазы в entrypoint) не может прочитать
|
||
оригинальный ключ (`root`, `0600`), а права на хосте ослаблять нельзя.
|
||
- Deploy-hook certbot дополнительно перекопирует сертификат и перезапускает
|
||
`coturn` при продлении — без этого TLS-TURN тихо остановился бы
|
||
обслуживать новые TLS-хендшейки примерно через 60 дней.
|
||
|
||
### Не сделано
|
||
- TURN over `443` — недостижимо без SNI-мультиплексора: порт уже занят
|
||
nginx (Docker port-publish), а coturn слушает в `network_mode: host` и не
|
||
может разделить с ним один и тот же сокет.
|
||
|
||
## [0.0.21] — 2026-08-02
|
||
|
||
Рычаги нагрузки медиа в админке: потолок качества публикации и лимит плиток.
|
||
|
||
### Добавлено
|
||
- Настройка инстанса «Потолок качества публикации видео» (без ограничения /
|
||
720p / 360p / 180p) — режет битрейт исходящего видео публикующего через
|
||
`publishDefaults` LiveKit, снижает нагрузку на его канал и устройство.
|
||
Дефолт — без ограничения, поведение существующих инсталляций не меняется.
|
||
- Настройка инстанса «Максимум плиток на экране» (25 / 16 / 9 / 4) — участники
|
||
сверх лимита уходят на следующую страницу сетки вместо подписки на их
|
||
видеотреки, меньше одновременных видеопотоков на канал и экран участника.
|
||
Дефолт — 25 (текущий максимум сетки 5×5), без изменений.
|
||
- Обе настройки доступны в новой карточке «Нагрузка» вкладки «Настройки»
|
||
админки и отдаются участнику вместе с токеном входа в конференцию — ещё до
|
||
подключения к комнате, чтобы применяться до публикации трека и не вызывать
|
||
переподключение уже вошедших участников при смене настройки.
|
||
|
||
## [0.0.20] — 2026-08-02
|
||
|
||
Сеть LiveKit: один UDP-порт вместо диапазона на 101 порт.
|
||
|
||
### Исправлено
|
||
- Весь медиа-трафик конференций шёл через userland-прокси Docker: диапазон
|
||
`54000-54100/udp` заставлял поднимать по отдельному процессу `docker-proxy`
|
||
на каждый порт. LiveKit переведён на `rtc.udp_port` (один порт,
|
||
мультиплексирование ICE-сессий по ufrag внутри самого сервера) — проброс
|
||
портов схлопнут до одного, TURN не затронут. Проверено локально
|
||
синтетической нагрузкой (`lk load-test`, 2 видео + 2 аудио publisher'а,
|
||
2 subscriber'а) — 0% потерь пакетов, ICE во всех сессиях выбирает новый
|
||
единственный порт.
|
||
|
||
## [0.0.19] — 2026-08-02
|
||
|
||
Правки по замечаниям к части B (комната конференции).
|
||
|
||
### Исправлено
|
||
- Очередь поднятых рук открывалась боковой панелью во весь экран на
|
||
мобильном — теперь компактный поповер над кнопкой (как «Вид»), размер
|
||
подстраивается под число записей, после ~10 строк список скроллится.
|
||
- Кнопки нижнего тулбара при сужении окна вылезали за края блока (задачи
|
||
B1/B2 добавили «Рука»/«Очередь», в тулбаре стало до 11 кнопок вместо
|
||
восьми) — теперь плавно уменьшаются на диапазоне 1200–600px вместо
|
||
жёсткого скачка на мобильный вид.
|
||
- Подпись «Мини-окно» на промежуточных ширинах переносилась на 2 строки и
|
||
делала эту кнопку выше соседних — ниже 1200px показывается короткое
|
||
«Мини».
|
||
- Эмодзи-поповер в чате красил все свои кнопки в зелёный цвет кнопки
|
||
«Отправить» (гонка специфичности CSS-селекторов) — исправлено; заодно
|
||
сетка приведена к ровным 5×6 без неполной строки, добавлены
|
||
🦾 🚀 🦞 💯 🤷♂️.
|
||
|
||
## [0.0.18] — 2026-08-01
|
||
|
||
Ограничение частоты запросов больше не блокирует вход целой конференции.
|
||
|
||
### Исправлено
|
||
- Лимит на резолв конференции и гостевой вход считался по адресу контейнера
|
||
nginx, а не клиента, — то есть «10 запросов в минуту» действовали на весь
|
||
сервер разом. Одиннадцатый человек, открывший ссылку в течение минуты,
|
||
получал отказ и видел «Не удалось найти конференцию» для существующей и
|
||
активной конференции. Теперь адрес берётся из заголовка, который nginx уже
|
||
передаёт.
|
||
- Счётчик считает только неудачные попытки — конференция не найдена или
|
||
пароль неверен. Именно так выглядит перебор номера, от которого защищает
|
||
ограничение; массовый вход по рабочей ссылке к нему отношения не имеет.
|
||
На общий поток с адреса оставлен потолок в 300 запросов в минуту.
|
||
|
||
## [0.0.17] — 2026-08-01
|
||
|
||
Массовый вход в систему и в конференцию перестаёт упираться в проверку пароля.
|
||
|
||
### Исправлено
|
||
- Проверка пароля больше не останавливает весь backend. Хэширование argon2 —
|
||
это десятки миллисекунд счёта, и выполнялось оно синхронно внутри
|
||
асинхронного обработчика: пока считался один пароль, процесс не обслуживал
|
||
ничего другого. На нагрузочном тесте 31.07 с примерно семью десятками
|
||
одновременных входов это дало p95 логина 7.28 секунды, p95 входа в
|
||
конференцию 7.06 секунды и 33 соединения к базе, висящих в открытой
|
||
транзакции при одном активном запросе. Страдали и посторонние запросы —
|
||
вход в конференцию отказывал, хотя пароль там не проверялся вовсе.
|
||
Теперь хэширование считается в пуле потоков.
|
||
- Параметры argon2id приведены к рекомендации OWASP (t=2, m=19 МБ, p=1)
|
||
вместо дефолтов библиотеки (t=3, m=64 МБ, p=4): 95 мс против 42 мс на
|
||
проверку. Прежнее значение `parallelism=4` вдобавок занимало все четыре
|
||
ядра сервера — те же, на которых работает медиа-сервер.
|
||
- Пароли, сохранённые со старыми параметрами, продолжают работать и
|
||
перевыпускаются автоматически при первом успешном входе.
|
||
|
||
## [0.0.16] — 2026-08-01
|
||
|
||
Роль организатора в комнате: поднятие руки с очередью и принудительный мьют.
|
||
|
||
### Добавлено
|
||
- Роль организатора прокинута в комнату — метаданные LiveKit-токена несут
|
||
подсказку `is_organizer` для UI (владелец конференции/администратор);
|
||
любое серверное действие организатора перепроверяется по владельцу
|
||
конференции в БД, метаданным токена для авторизации не доверяем.
|
||
- Поднятие руки: кнопка «Рука» в тулбаре (у всех участников, с общим
|
||
счётчиком), бейдж на плитке говорящего, видимый всем, и панель «Очередь»
|
||
для организатора — участники в порядке поднятия руки, с возможностью
|
||
опустить любую руку. Состояние — в Redis (не в БД): очередь существует
|
||
ровно во время звонка. Участник, вышедший из конференции, автоматически
|
||
исчезает из очереди; переподключение место не теряет.
|
||
- Принудительный мьют: организатор может выключить микрофон или камеру
|
||
любого участника — кнопки на его плитке. Участник получает уведомление,
|
||
что его выключил организатор, и может включить себя обратно сразу тем же
|
||
тулбаром.
|
||
- Эмодзи скачущего коня 🐎 в наборе смайликов чата.
|
||
|
||
### Изменено
|
||
- Транспорт чата (`WS /conferences/{id}/chat`) расширен: очередь поднятых
|
||
рук и уведомления о мьюте едут по тому же аутентифицированному
|
||
соединению, что и сообщения чата — без нового эндпоинта.
|
||
|
||
Миграций БД нет — новое состояние (очередь поднятых рук) хранится в Redis,
|
||
не в Postgres.
|
||
|
||
## [0.0.15] — 2026-07-30
|
||
|
||
Шесть доработок UI комнаты конференции.
|
||
|
||
### Добавлено
|
||
- Выбор эмодзи в чате — поповер с небольшим собственным набором популярных
|
||
эмодзи (без библиотеки-пикера), вставка в позицию курсора.
|
||
- Кнопка микрофона в мини-окне (Document PiP) — раньше в PiP не было
|
||
никакого способа замьютить себя, не разворачивая основное окно.
|
||
- Закрепление участника доступно из любого режима показа («Стандарт»,
|
||
«Плитки», «Живые плитки») — раньше булавка рендерилась только в
|
||
«Стандарте». Само закрепление автоматически переключает вид на «Стандарт»
|
||
(закреплённого иначе негде показать крупно); открепление вид назад не
|
||
меняет.
|
||
|
||
### Изменено
|
||
- Участник заходит в конференцию с выключенными микрофоном и камерой,
|
||
включает их сам уже в комнате.
|
||
- В топбаре комнаты вместо технического `room_name` LiveKit (вида
|
||
`hC-Dmos9KEM`) показывается название конференции; при его отсутствии —
|
||
«Конференция без названия». Номер и ссылка-приглашение не задеты.
|
||
|
||
## [0.0.14] — 2026-07-28
|
||
|
||
TURN-фолбэк для участников из сетей с жёстким NAT.
|
||
|
||
### Исправлено
|
||
- LiveKit теперь анонсирует клиентам внешний TURN-сервер (`rtc.turn_servers`
|
||
в конфигурации, UDP и TCP на 3478). Раньше coturn поднимался и был healthy,
|
||
но клиенты о нём не знали: внешний TURN в конфиге объявлен не был, встроенный
|
||
выключен, а фронтенд `iceServers` не задаёт. За всё время работы в логах
|
||
coturn не было ни одной аллокации — то есть relay не использовался никогда,
|
||
и участники из сетей, где прямое UDP-соединение не проходит, теряли связь
|
||
(`PEER_CONNECTION_DISCONNECTED`). На нагрузочном тесте 28.07 все такие
|
||
разрывы пришлись на внешних участников и ни одного — на офисных.
|
||
- Credentials TURN генерируются по механизму TURN REST API из общего
|
||
`TURN_STATIC_AUTH_SECRET`, то есть тот же секрет, что и у coturn.
|
||
|
||
### Изменено
|
||
- `deploy/render-templates.sh` подставляет `TURN_EXTERNAL_IP` и
|
||
`TURN_STATIC_AUTH_SECRET` также в конфигурацию LiveKit.
|
||
- Раздел 8 руководства по развёртыванию переписан: TURN больше не «план на
|
||
будущее», а рабочая конфигурация. Отдельно описано правило ufw для
|
||
relay-диапазона `49160:49200/udp` — без него TURN отвечает на запросы, но
|
||
релей не работает, причём в логах coturn при этом тишина.
|
||
|
||
TURN over TLS (5349/443) по-прежнему не настроен: требует монтирования
|
||
сертификата в coturn. Неработающий `turns:` намеренно не анонсируется, иначе
|
||
клиент ждал бы таймаута перед переходом к рабочему кандидату.
|
||
|
||
## [0.0.13] — 2026-07-28
|
||
|
||
Снижение нагрузки на сеть: клиент перестаёт получать полное качество всех
|
||
чужих камер независимо от того, какого размера плитка на экране.
|
||
|
||
### Изменено
|
||
- Включены `adaptiveStream` и `dynacast` в опциях комнаты. Оба флага в LiveKit
|
||
выключены по умолчанию, из-за чего каждый участник был подписан на полное
|
||
качество всех чужих треков, а каждый паблишер слал все слои симулкаста, даже
|
||
когда их никто не смотрит. Теперь качество подписки выбирается по фактическому
|
||
размеру плитки, а неотрисованные треки уходят в паузу. На локальном стенде
|
||
(9 участников, паблишеры 720p) входящий поток одного клиента упал с
|
||
11 110 до 778 кбит/с.
|
||
|
||
Вместе с этим начинает экономить уже написанный код, который до сих пор не
|
||
давал выигрыша: «скрыть остальных» не рендерит карусель (исходящий трафик
|
||
LiveKit 0.76 → 0.03 Мбит/с), пагинация сетки участников рендерит только
|
||
текущую страницу, а пауза чужого видео в свёрнутой вкладке работает лишь
|
||
при включённом `adaptiveStream`.
|
||
- Контейнеры больше не пересобирают окружение Python при запуске: во все
|
||
вызовы `uv run` в прод-путях (CMD образа, `command`/`entrypoint`/`healthcheck`
|
||
сервисов, миграции и seed в `install.sh`) добавлен `--no-sync`. Раньше
|
||
окружение, собранное на этапе build с `--no-dev`, при каждом старте
|
||
синхронизировалось заново и подтягивало dev-группу: ~26 МБ загрузок,
|
||
замедленный старт и ruff/mypy/pytest в рантайме. Healthcheck'и делали то же
|
||
самое каждые 15 секунд всю жизнь контейнера. Размер `/app/.venv` после
|
||
запуска: 574 → 456 МБ. Локальная разработка не затронута — там dev-группа
|
||
нужна и ставится как раньше.
|
||
|
||
## [0.0.12] — 2026-07-28
|
||
|
||
Разблокировка backend под нагрузкой: вход в конференцию перестаёт отваливаться,
|
||
когда участники включают микрофоны.
|
||
|
||
### Исправлено
|
||
- Обработчик webhook `track_published` больше не запускает Track Egress внутри
|
||
своей транзакции. Раньше каждый опубликованный микрофон уходил в сетевой
|
||
вызов, а на инстансе без профиля `transcribe` (egress-сервиса в деплое нет)
|
||
этот вызов висел 20–25 секунд, всё это время удерживая соединение с БД. На
|
||
нагрузочном тесте с 19 участниками пул соединений выгребался за секунды, и
|
||
вход в конференцию начинал отвечать 500. Теперь запуск уходит в фоновую
|
||
задачу со своей сессией, а webhook отвечает сразу — LiveKit перестаёт копить
|
||
очередь доставки и терять события.
|
||
- Track Egress не запускается вовсе, если транскрибация выключена в настройках
|
||
инстанса — тот же guard, что уже был в обработчике `room_finished`.
|
||
- Запуск Track Egress ограничен таймаутом (по умолчанию 3 секунды) вместо
|
||
ожидания собственного таймаута LiveKit.
|
||
|
||
### Изменено
|
||
- Пул соединений с БД задаётся явно (`DB_POOL_SIZE`, `DB_MAX_OVERFLOW`,
|
||
`DB_POOL_TIMEOUT`) вместо дефолта SQLAlchemy 5 + 10. Считайте бюджет на весь
|
||
инстанс: каждый воркер держит свой пул, и сумма должна оставаться заметно
|
||
ниже `max_connections` у Postgres.
|
||
- Backend запускается с несколькими процессами uvicorn (`UVICORN_WORKERS`,
|
||
по умолчанию 2). Одиночный процесс означал, что любой блокирующий вызов
|
||
останавливает и параллельные запросы, и WS-чат всех участников. Дефолт 2, а
|
||
не по числу ядер: на четырёхъядерном сервере ядра делятся с LiveKit.
|
||
|
||
## [0.0.11] — 2026-07-28
|
||
|
||
Выбор режима показа участников в конференции, скрытие остальных и круглая
|
||
иконка участника при выключенной камере.
|
||
|
||
### Добавлено
|
||
- Выбор режима показа участников на сцене вместо автоматической раскладки:
|
||
«Стандарт» (крупная плитка плюс карусель остальных сбоку, как было),
|
||
«Плитки» (все участники равными плитками) и «Живые плитки» (плитками —
|
||
только те, у кого включена камера, остальные в карусели). Кто именно
|
||
показан крупно в «Стандарте», по-прежнему решает прежняя логика фокуса
|
||
(новая демонстрация → закреплённый → говорящий с видео → говорящий).
|
||
Выбор сохраняется между заходами в комнату.
|
||
- Скрытие остальных участников — на сцене остаётся только основная плитка.
|
||
Скрыть можно из меню «Вид», из шторки настроек на мобильном и кнопкой над
|
||
колонкой миниатюр; вернуть — кнопкой «Показать остальных (N)», которая
|
||
видна всё время, пока кто-то скрыт. В режиме «Плитки» скрывать нечего,
|
||
переключатель там заблокирован.
|
||
- Переключатель вида: кнопка «Вид» с поповером в тулбаре на широком экране;
|
||
на мобильном — секция «Вид» в шторке настроек, чтобы не добавлять шестую
|
||
кнопку в уже ужатый тулбар.
|
||
|
||
### Изменено
|
||
- Демонстрация экрана временно перебивает выбранный режим: пока идёт шэр,
|
||
сцена ведёт себя как «Стандарт», а по его завершении возвращается к
|
||
выбранному виду.
|
||
- Равномерная сетка плиток стала пригодна для телефона: 2 колонки от 360px
|
||
ширины и портретная раскладка 2×3 вместо двух плиток на страницу, которые
|
||
давал набор раскладок библиотеки. Лишние участники уходят на следующую
|
||
страницу — листается свайпом и стрелками.
|
||
|
||
### Исправлено
|
||
- Иконка участника при выключенной камере больше не превращается в эллипс:
|
||
размер считается от узкой стороны плитки (container-запросы), а не как
|
||
проценты от разных сторон. В крупной плитке иконка занимает примерно её
|
||
половину, в миниатюрах карусели — всю узкую сторону с отступом 5px; на
|
||
большой плитке фокуса иконка больше не упирается в прежний потолок 96px.
|
||
- Мини-плеер в Chrome открывался на самом пользователе вместо того, что он
|
||
видел крупно: сцена в PiP-окне — отдельный экземпляр компонента и выбирала
|
||
фокус с нуля, доходя до фолбэка «показать себя». Теперь ключ фокуса
|
||
переживает переезд в мини-окно и обратно. В Safari (video-фолбэк вместо
|
||
Document PiP) поведение не менялось.
|
||
|
||
## [0.0.10] — 2026-07-28
|
||
|
||
Разбивка метрик по контейнерам в Grafana и плашка с характеристиками сервера.
|
||
|
||
### Добавлено
|
||
- Метрики по контейнерам (CPU/память/сеть каждого отдельно) — замена
|
||
`cAdvisor`, несовместимого с containerd-снапшоттером Docker Engine на
|
||
сервере `1gb` (убран в `0.0.9`). Свой минимальный сервис
|
||
`container-exporter` (`deploy/monitoring/container-exporter/`) опрашивает
|
||
Docker Engine API параллельно в фоновой задаче, независимо от
|
||
storage-driver'а. Доступ к докер-сокету изолирован через
|
||
`docker-socket-proxy` — экспортеру разрешены только `GET`-запросы по
|
||
контейнерам, любые изменяющие запросы блокируются на уровне прокси.
|
||
Панели «Топ контейнеров по CPU/памяти» вернулись в дашборд
|
||
«Хост и контейнеры».
|
||
- Плашка «Характеристики сервера» в том же дашборде: живые «Ядра CPU» /
|
||
«Объём RAM» / «Диск (/)» из `node-exporter`, GPU — из новой info-метрики
|
||
backend `vidconf_host_info` (данные `install.sh`, `HW_*` в `.env`).
|
||
|
||
## [0.0.9] — 2026-07-28
|
||
|
||
Метрики CPU/RAM/диска хоста в Grafana; дефолтные подвкладки конференций и
|
||
пользователей в админке.
|
||
|
||
### Добавлено
|
||
- Мониторинг железа: `node-exporter` (CPU/RAM/диск/сеть/load average хоста)
|
||
— профиль compose `monitoring`, без публикации портов наружу. Новый
|
||
дашборд Grafana «Хост и контейнеры». Три новых алерта Prometheus:
|
||
`HostMemoryLow`, `HostDiskLow`, `HostCpuHigh` (пороги — под сервер `1gb`:
|
||
8 ГБ RAM, 4 CPU, 50 ГБ диска). `cAdvisor` (разбивка по контейнерам)
|
||
сознательно не включён — несовместим с containerd-снапшоттером Docker
|
||
Engine на `1gb`, см. `docs/deploy/monitoring.md`.
|
||
|
||
### Изменено
|
||
- Админка, вкладка «Конференции»: вкладка «Все» перенесена в конец списка
|
||
фильтров (после «Завершённые»), по умолчанию открываются «Запланированные».
|
||
- Админка, вкладка «Пользователи»: по умолчанию открываются «Активные»
|
||
(было «Все»).
|
||
|
||
## [0.0.8] — 2026-07-27
|
||
|
||
Админка: несколько mail-доменов для регистрации, фильтр и поиск по команде в
|
||
списке пользователей, исправлен баг сохранения настроек.
|
||
|
||
### Исправлено
|
||
- Сохранение любой настройки инстанса блокировалось ошибкой «уровень AI
|
||
недоступен», даже если AI-модуль (транскрибация/суммаризация) выключен и
|
||
пользователь уровень не трогал — фронт отправлял в `PUT /admin/settings`
|
||
весь набор полей целиком, включая уже сохранённый `ai_level`. Теперь
|
||
доступность уровня проверяется только при его фактической смене; фронт
|
||
отправляет в запросе лишь реально изменённые поля.
|
||
|
||
### Добавлено
|
||
- «Эталон mail-домена» в настройках инстанса поддерживает несколько
|
||
доменов вместо одного — регистрация проходит с email с любого домена из
|
||
списка. Старое значение из БД (один домен) читается без миграции и
|
||
переписывается в новый формат при первом же сохранении.
|
||
- В списке пользователей админки — вкладки «Активные»/«Заблокированные»/
|
||
«Все» перед полем поиска.
|
||
- Поиск по пользователям учитывает и название команды — пользователи без
|
||
команды по-прежнему остаются в общей выдаче.
|
||
|
||
## [0.0.7] — 2026-07-27
|
||
|
||
Настройки устройств: выбор аудиовывода и компактная шторка на мобильном.
|
||
|
||
### Добавлено
|
||
- Выбор устройства аудиовывода (динамики/наушники/Bluetooth-гарнитура) в
|
||
«Настройках устройств» рядом с микрофоном и камерой. Выбор переживает
|
||
перезаход в комнату. Список показывается только там, где браузер умеет
|
||
им управлять (Android Chrome и другие Chromium на десктопе/мобильном);
|
||
в iOS Safari поддержки нет вовсе — вместо списка короткая подсказка, что
|
||
переключение звука там на стороне системы.
|
||
- На мобильной ширине «Настройки устройств» открываются компактной шторкой
|
||
снизу вместо модалки по центру экрана — закрывается свайпом вниз, тапом
|
||
вне панели или Escape. На десктопе поведение не изменилось.
|
||
|
||
### Примечания по эксплуатации
|
||
- Управлять маршрутизацией звука (принудительно включить громкую связь или
|
||
перекинуть звук на Bluetooth) из браузера нельзя в принципе — это делает
|
||
ОС, веб-страница может только выбрать устройство вывода там, где браузер
|
||
это позволяет (`setSinkId`).
|
||
|
||
## [0.0.6] — 2026-07-27
|
||
|
||
Управление фокусом основного окна конференции: участника можно закрепить, а
|
||
без закрепления окно само следует за говорящим. Плюс общий футер на всех
|
||
страницах сервиса.
|
||
|
||
### Добавлено
|
||
- Закрепление участника в основном окне — кнопка-булавка на плитке.
|
||
Закреплённый участник держит фокус, даже когда говорят другие; повторное
|
||
нажатие снимает закрепление, как и выход закреплённого из комнаты.
|
||
Закреплённая плитка помечена рамкой и подсвеченной булавкой; на
|
||
тач-устройствах булавка видна без наведения.
|
||
- Общий футер на всех страницах, кроме комнаты конференции: версия инстанса
|
||
и копирайт со ссылкой на автора.
|
||
|
||
### Изменения
|
||
- Основное окно конференции следует за активным говорящим, а не удерживает
|
||
выбранного один раз участника. Фокус не переключается на короткие реплики
|
||
(удержание 1.2 с) и не уводится с идущей демонстрации экрана. Среди
|
||
нескольких одновременно говорящих крупно показывается тот, у кого включена
|
||
камера. Мини-плеер (Document PiP) работает как раньше.
|
||
- Демонстрация экрана по-прежнему главнее всего: она перебивает и
|
||
закрепление, а после её завершения фокус возвращается на закреплённого
|
||
участника.
|
||
- Футер админки заменён общим компонентом; в брендовой панели экранов входа
|
||
строка копирайта с захардкоженным годом убрана — год теперь берётся
|
||
текущий.
|
||
|
||
### Исправлено
|
||
- Календарь: «г.» после года убрана и из подписей дней в самой сетке (видны
|
||
как всплывающая подсказка и в accessibility-дереве) — в недельном и
|
||
месячном виде. В 0.0.4 это было исправлено только для подписи периода.
|
||
|
||
## [0.0.5] — 2026-07-27
|
||
|
||
Контактный адрес инстанса: настраиваемый адрес для ответов на письма
|
||
(уходят от `no-reply@`) и кнопка проверки почтовой конфигурации из
|
||
админки.
|
||
|
||
### Добавлено
|
||
- Настройка «Контактный адрес» в админке (вкладка «Настройки») — email
|
||
подставляется в заголовок `Reply-To` писем подтверждения регистрации,
|
||
приглашений на конференции и саммари; при выключенной настройке
|
||
`Reply-To` не проставляется, поведение как раньше.
|
||
- Кнопка «Отправить тестовое письмо» — синхронная проверка почтовой
|
||
конфигурации без регистрации фиктивного пользователя. Получатель по
|
||
умолчанию — email текущего администратора, можно указать другой.
|
||
Результат виден сразу: успех либо текст ошибки транспорта (сервер
|
||
недоступен/получатель отклонён), без логина и пароля SMTP в ответе.
|
||
|
||
## [0.0.4] — 2026-07-26
|
||
|
||
Календарь конференций: перестроен в раскладку «календарь во всю ширину плюс
|
||
модальные окна» — карточки событий разгружены, подробности вынесены во
|
||
всплывающее окно.
|
||
|
||
### Изменения
|
||
- Форма создания и редактирования конференции больше не занимает постоянную
|
||
колонку справа, а открывается в модальном окне — по кнопке «Новая
|
||
конференция», по карандашику на карточке события или из информационного
|
||
окна. Добавлен общий хук `useModalDismiss` (закрытие по Escape и возврат
|
||
фокуса на элемент, из которого окно открыли).
|
||
- Блок календаря занимает всю ширину страницы.
|
||
- Карточка события в сетке содержит только название (обрезается многоточием и
|
||
больше не вылезает за границы ячейки) и — у организатора — кнопку
|
||
редактирования. Время, номер и статус переехали в информационное окно.
|
||
- Клик по карточке события всегда открывает информационное окно, в том числе
|
||
для своих конференций: редактирование стало отдельным действием.
|
||
- Информационное окно вхождения дополнено статус-бейджем и кнопкой
|
||
«Редактировать» для организатора.
|
||
|
||
### Исправления
|
||
- Подпись периода в недельном и месячном виде больше не содержит «г.»
|
||
(форматирование через `Intl.DateTimeFormat` вместо локали FullCalendar).
|
||
|
||
## [0.0.3] — 2026-07-26
|
||
|
||
Мобильная версия: адаптивная вёрстка для узких экранов на ключевых экранах
|
||
сервиса, найденная и поправленная по итогам ручного прохода по мобильному
|
||
интерфейсу.
|
||
|
||
### Исправления
|
||
- Вход/регистрация (`AuthLayout`): заголовок «Ваша инфраструктура» вылезал за
|
||
край брендовой панели на узких экранах — кегль теперь адаптивно уменьшается
|
||
(`clamp()`) вместо обрезки.
|
||
- Комната конференции: топбар и тулбар не помещались на мобильной ширине
|
||
(тулбар с 8 кнопками физически шире экрана, крайние кнопки были обрезаны без
|
||
возможности до них докрутить). На мобильном тулбар сокращён до
|
||
микрофона/камеры/устройств/чата/выхода (демонстрация экрана,
|
||
полноэкранный режим и мини-окно скрыты — редко нужны с телефона), в топбаре
|
||
скрыты название конференции и строка статуса, а чип «№ … / Пригласить»
|
||
оставлен — с телефона он нужнее.
|
||
- Админка: заголовок и 4 вкладки раздела не помещались в ширину (вкладки
|
||
переносятся в 2 ряда, поясняющий текст под заголовком скрыт на мобильном);
|
||
таблицы «Конференции» и «Пользователи» обрезались вбок — на мобильном в них
|
||
остаются только ключевые колонки, название конференции стало кнопкой,
|
||
открывающей то же окно редактирования, что и раньше (туда же добавлена
|
||
кнопка удаления — на мобильном столбец действий скрыт).
|
||
- Подключение к конференции (`/join`): кнопка «Ввести другую ссылку или
|
||
номер» на экране завершённой конференции вылезала за карточку на узких
|
||
экранах (единственная кнопка на этом флоу без формы-обёртки, задающей ей
|
||
ширину, а `.btn` держит `white-space: nowrap`).
|
||
|
||
## [0.0.2] — 2026-07-26
|
||
|
||
Релиз воспроизводимого боевого развёртывания: конфигурация деплоя перенесена в
|
||
репозиторий как источник истины, устранён инцидент безопасности, добавлена
|
||
пошаговая инструкция для девопсов.
|
||
|
||
### Безопасность
|
||
- `postgres` и `redis` сняты с публикации на `0.0.0.0` и привязаны к `127.0.0.1`
|
||
(docker писал DNAT в обход `ufw`, порты были открыты в интернет).
|
||
- Порты `backend`, `livekit`-signaling, `prometheus`, `grafana`, `llm` также
|
||
переведены на loopback — наружу остаются только `80/443/7881` (+ UDP `54000-54100`).
|
||
- Для `redis` включена обязательная аутентификация (`--requirepass`); пароль
|
||
проброшен во всех потребителей (backend, worker, exporter, livekit, egress).
|
||
|
||
### Развёртывание и инфраструктура
|
||
- `nginx`/`coturn`/`livekit`: TLS на 443, http→https редирект, ACME-webroot;
|
||
конфиги вынесены в `*.template` и рендерятся через `deploy/render-templates.sh`
|
||
(`envsubst`). Секреты — только из `.env`, обязательны (`${VAR:?}`), без
|
||
небезопасных дефолтов.
|
||
- `--env-file .env` добавлен во все вызовы `docker compose` (без него compose не
|
||
подхватывал корневой `.env` и брал dev-дефолты — корень бага «конференции не
|
||
работают у других» и `WARN: LIVEKIT_API_KEY not set`).
|
||
- Мониторинг развязан от опционального LLM: `job=llm`/алерт `LlmDown`
|
||
закомментированы для профилей без `llm`/`llm-gpu`.
|
||
- `install.sh`: fail-fast валидация — установка останавливается с понятной
|
||
ошибкой, если на боевом домене остались dev-плейсхолдеры (`example.com`,
|
||
`ws://localhost`, `devkey` и т.п.), вместо тихого подъёма нерабочего прода.
|
||
- `install.sh`: флаг `--monitoring` (Grafana/Prometheus раньше не поднимались
|
||
ни в одном пресете).
|
||
- `.env.example` покрывает все параметры деплоя; дефолтные профили —
|
||
`media,monitoring`.
|
||
|
||
### Исправления
|
||
- `nginx`: `/openapi.json` больше не перехватывается SPA-фолбэком, а отдаётся
|
||
схемой backend.
|
||
- Предупреждение в `.env.example`: `LIVEKIT_PUBLIC_URL` в HTTPS-проде обязан
|
||
быть `wss://`.
|
||
|
||
### Документация
|
||
- Добавлено `docs/deploy/DEPLOYMENT.md` — полное руководство от голой Ubuntu 24
|
||
до рабочего `https://<домен>`: SSL, `.env`, профили, траблшутинг, доступ к
|
||
мониторингу через ssh-туннель, описание панелей, runbook включения
|
||
транскрибации/суммаризации. Ссылки добавлены в `README.md`.
|
||
- `docs/deploy/install.md`: задокументирован флаг `--monitoring`.
|
||
|
||
### Примечания по эксплуатации
|
||
- На боевом сервере исправлен certbot deploy-hook (`nginx -s reload` → `restart`):
|
||
без этого после автопродления сертификата nginx продолжал бы отдавать старый.
|
||
- TURN-раздача клиентам (`rtc.turn_servers` + TURN/TLS на 443) остаётся
|
||
опциональной — кросс-сетевое медиа проверено вживую и работает без неё;
|
||
нужна только для экстремального NAT (CGNAT/жёсткий корпоративный firewall).
|
||
|
||
## [0.0.1]
|
||
|
||
Первоначальная версия VidConf.
|
||
|
||
[0.0.10]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.10
|
||
[0.0.9]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.9
|
||
[0.0.8]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.8
|
||
[0.0.7]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.7
|
||
[0.0.6]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.6
|
||
[0.0.5]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.5
|
||
[0.0.4]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.4
|
||
[0.0.3]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.3
|
||
[0.0.2]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.2
|
||
[0.0.1]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.1
|