Merge pull request #94 from cyber-debug/master

perf: снизить idle CPU и удалить Docker-поддержку
This commit is contained in:
zarazaex
2026-06-07 01:16:40 +03:00
committed by GitHub
47 changed files with 233 additions and 1997 deletions

View File

@@ -1,19 +0,0 @@
.git
.gitignore
Dockerfile
docker-compose*.yml
code/
doc/
asset/
*.log
*.tmp
tmp/
olcrtc
build/
dist/
coverage.out
GEMINI.md

View File

@@ -31,7 +31,7 @@ body:
id: version
attributes:
label: Версия olcrtc
description: Тег релиза, имя ветки и git commit (`git rev-parse --short HEAD`). Если собирал из docker - пиши тег образа.
description: Тег релиза, имя ветки и git commit (`git rev-parse --short HEAD`).
placeholder: "main @ a1b2c3d / v0.x.y / refactor/universal-carrier @ ef01234"
validations:
required: true
@@ -42,8 +42,8 @@ body:
label: Способ сборки/запуска
options:
- Native
- Docker / docker compose
- Podman / script
- Native binary
- Mobile / gomobile
- Other
validations:
required: true
@@ -170,8 +170,6 @@ body:
description: |
Полные логи с момента запуска до момента ошибки.
Если запуск через systemd - `journalctl -u olcrtc -n 500 --no-pager`.
Если docker - `docker logs <container>`.
Если запуск через podman - `podman logs -f <container> `.
render: shell
validations:
required: true
@@ -205,6 +203,6 @@ body:
id: extra
attributes:
label: Дополнительно
description: Что ещё может быть полезно - версия podman/docker, нестандартный конфиг (failover, traffic shaping, lifecycle), временные паттерны (бьёт раз в сутки, после сна и т.п.), известные обходы.
description: Что ещё может быть полезно - нестандартный конфиг (failover, traffic shaping, lifecycle), временные паттерны (бьёт раз в сутки, после сна и т.п.), известные обходы.
validations:
required: false

View File

@@ -38,7 +38,7 @@
- [ ] session / app (`internal/app/session`, `pkg/`)
- [ ] config / CLI / URI / sub
- [ ] mobile (`mobile/`)
- [ ] CI / Docker / mage
- [ ] CI / mage
- [ ] docs / examples
- [ ] другое:

View File

@@ -1,59 +0,0 @@
# syntax=docker/dockerfile:1.7
ARG GO_VERSION=1.26
ARG ALPINE_VERSION=3.22
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build
WORKDIR /src
RUN apk add --no-cache ca-certificates git
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
ARG TARGETOS=linux
ARG TARGETARCH=amd64
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags="-s -w" -o /out/olcrtc ./cmd/olcrtc
FROM alpine:${ALPINE_VERSION} AS runtime
RUN apk add --no-cache ca-certificates ffmpeg tzdata && \
addgroup -S olcrtc && \
mkdir -p /usr/share/olcrtc /var/lib/olcrtc && \
adduser -S -D -h /var/lib/olcrtc -s /sbin/nologin -G olcrtc olcrtc && \
chown -R olcrtc:olcrtc /usr/share/olcrtc /var/lib/olcrtc
COPY --chown=olcrtc:olcrtc data /usr/share/olcrtc
COPY --from=build /out/olcrtc /usr/local/bin/olcrtc
COPY script/docker/olcrtc-entrypoint.sh /usr/local/bin/olcrtc-entrypoint
COPY script/docker/olcrtc-healthcheck.sh /usr/local/bin/olcrtc-healthcheck
RUN chmod 0755 /usr/local/bin/olcrtc /usr/local/bin/olcrtc-entrypoint /usr/local/bin/olcrtc-healthcheck
USER olcrtc:olcrtc
WORKDIR /var/lib/olcrtc
ENV OLCRTC_MODE=srv \
OLCRTC_CARRIER= \
OLCRTC_TRANSPORT=datachannel \
OLCRTC_DATA_DIR=/usr/share/olcrtc \
OLCRTC_DNS=8.8.8.8:53 \
OLCRTC_KEY_FILE=/var/lib/olcrtc/key.hex \
OLCRTC_SOCKS_HOST=127.0.0.1 \
OLCRTC_SOCKS_PORT=8808 \
OLCRTC_FFMPEG=ffmpeg
VOLUME ["/var/lib/olcrtc"]
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
CMD ["/usr/local/bin/olcrtc-healthcheck"]
ENTRYPOINT ["/usr/local/bin/olcrtc-entrypoint"]

View File

@@ -164,8 +164,8 @@ func TestRunWithArgsAppliesTransportDefaults(t *testing.T) {
oldRunSession := runSession
t.Cleanup(func() { runSession = oldRunSession })
runSession = func(_ context.Context, cfg session.Config) error {
if cfg.VP8.FPS != 60 || cfg.VP8.BatchSize != 64 {
t.Errorf("VP8 defaults = fps %d batch %d, want 60/64", cfg.VP8.FPS, cfg.VP8.BatchSize)
if cfg.VP8.FPS != 30 || cfg.VP8.BatchSize != 64 {
t.Errorf("VP8 defaults = fps %d batch %d, want 30/64", cfg.VP8.FPS, cfg.VP8.BatchSize)
}
return nil
}
@@ -204,8 +204,8 @@ func TestRunWithArgsFailoverProfiles(t *testing.T) {
var seen []string
runSession = func(_ context.Context, cfg session.Config) error {
seen = append(seen, cfg.Auth+"/"+cfg.Transport)
if cfg.Auth == "wbstream" && (cfg.VP8.FPS != 60 || cfg.VP8.BatchSize != 64) {
t.Errorf("VP8 defaults = fps %d batch %d, want 60/64", cfg.VP8.FPS, cfg.VP8.BatchSize)
if cfg.Auth == "wbstream" && (cfg.VP8.FPS != 30 || cfg.VP8.BatchSize != 64) {
t.Errorf("VP8 defaults = fps %d batch %d, want 30/64", cfg.VP8.FPS, cfg.VP8.BatchSize)
}
return errBoom
}

View File

@@ -1,43 +0,0 @@
services:
olcrtc-client:
build:
context: .
image: olcrtc/client:local
container_name: olcrtc-client
restart: unless-stopped
network_mode: host
environment:
OLCRTC_MODE: cnc
OLCRTC_CARRIER: "${OLCRTC_CARRIER:?set OLCRTC_CARRIER (jitsi, telemost, wbstream, none)}"
OLCRTC_TRANSPORT: "${OLCRTC_TRANSPORT:-datachannel}"
OLCRTC_ROOM_ID: "${OLCRTC_ROOM_ID:?set OLCRTC_ROOM_ID to the server room}"
OLCRTC_KEY: "${OLCRTC_KEY:?set OLCRTC_KEY to the server encryption key}"
OLCRTC_KEY_FILE: "${OLCRTC_KEY_FILE:-/var/lib/olcrtc/key.hex}"
OLCRTC_DNS: "${OLCRTC_DNS:-8.8.8.8:53}"
OLCRTC_SOCKS_HOST: "${OLCRTC_SOCKS_HOST:-127.0.0.1}"
OLCRTC_SOCKS_PORT: "${OLCRTC_SOCKS_PORT:-8808}"
OLCRTC_SOCKS_USER: "${OLCRTC_SOCKS_USER:-}"
OLCRTC_SOCKS_PASS: "${OLCRTC_SOCKS_PASS:-}"
OLCRTC_VIDEO_W: "${OLCRTC_VIDEO_W:-0}"
OLCRTC_VIDEO_H: "${OLCRTC_VIDEO_H:-0}"
OLCRTC_VIDEO_FPS: "${OLCRTC_VIDEO_FPS:-0}"
OLCRTC_VIDEO_BITRATE: "${OLCRTC_VIDEO_BITRATE:-}"
OLCRTC_VIDEO_HW: "${OLCRTC_VIDEO_HW:-none}"
OLCRTC_VIDEO_CODEC: "${OLCRTC_VIDEO_CODEC:-qrcode}"
OLCRTC_VIDEO_QR_SIZE: "${OLCRTC_VIDEO_QR_SIZE:-0}"
OLCRTC_VIDEO_QR_RECOVERY: "${OLCRTC_VIDEO_QR_RECOVERY:-low}"
OLCRTC_VIDEO_TILE_MODULE: "${OLCRTC_VIDEO_TILE_MODULE:-0}"
OLCRTC_VIDEO_TILE_RS: "${OLCRTC_VIDEO_TILE_RS:-0}"
OLCRTC_VP8_FPS: "${OLCRTC_VP8_FPS:-0}"
OLCRTC_VP8_BATCH: "${OLCRTC_VP8_BATCH:-0}"
OLCRTC_SEI_FPS: "${OLCRTC_SEI_FPS:-0}"
OLCRTC_SEI_BATCH: "${OLCRTC_SEI_BATCH:-0}"
OLCRTC_SEI_FRAG: "${OLCRTC_SEI_FRAG:-0}"
OLCRTC_SEI_ACK: "${OLCRTC_SEI_ACK:-0}"
OLCRTC_DEBUG: "${OLCRTC_DEBUG:-false}"
volumes:
- olcrtc-client-state:/var/lib/olcrtc
init: true
volumes:
olcrtc-client-state:

View File

@@ -1,40 +0,0 @@
services:
olcrtc-server:
build:
context: .
image: olcrtc/server:local
container_name: olcrtc-server
restart: unless-stopped
environment:
OLCRTC_MODE: srv
OLCRTC_CARRIER: "${OLCRTC_CARRIER:?set OLCRTC_CARRIER (jitsi, telemost, wbstream, none)}"
OLCRTC_TRANSPORT: "${OLCRTC_TRANSPORT:-datachannel}"
OLCRTC_ROOM_ID: "${OLCRTC_ROOM_ID:-}"
OLCRTC_KEY: "${OLCRTC_KEY:-}"
OLCRTC_KEY_FILE: "${OLCRTC_KEY_FILE:-/var/lib/olcrtc/key.hex}"
OLCRTC_DNS: "${OLCRTC_DNS:-8.8.8.8:53}"
OLCRTC_SOCKS_PROXY: "${OLCRTC_SOCKS_PROXY:-}"
OLCRTC_SOCKS_PROXY_PORT: "${OLCRTC_SOCKS_PROXY_PORT:-1080}"
OLCRTC_VIDEO_W: "${OLCRTC_VIDEO_W:-0}"
OLCRTC_VIDEO_H: "${OLCRTC_VIDEO_H:-0}"
OLCRTC_VIDEO_FPS: "${OLCRTC_VIDEO_FPS:-0}"
OLCRTC_VIDEO_BITRATE: "${OLCRTC_VIDEO_BITRATE:-}"
OLCRTC_VIDEO_HW: "${OLCRTC_VIDEO_HW:-none}"
OLCRTC_VIDEO_CODEC: "${OLCRTC_VIDEO_CODEC:-qrcode}"
OLCRTC_VIDEO_QR_SIZE: "${OLCRTC_VIDEO_QR_SIZE:-0}"
OLCRTC_VIDEO_QR_RECOVERY: "${OLCRTC_VIDEO_QR_RECOVERY:-low}"
OLCRTC_VIDEO_TILE_MODULE: "${OLCRTC_VIDEO_TILE_MODULE:-0}"
OLCRTC_VIDEO_TILE_RS: "${OLCRTC_VIDEO_TILE_RS:-0}"
OLCRTC_VP8_FPS: "${OLCRTC_VP8_FPS:-0}"
OLCRTC_VP8_BATCH: "${OLCRTC_VP8_BATCH:-0}"
OLCRTC_SEI_FPS: "${OLCRTC_SEI_FPS:-0}"
OLCRTC_SEI_BATCH: "${OLCRTC_SEI_BATCH:-0}"
OLCRTC_SEI_FRAG: "${OLCRTC_SEI_FRAG:-0}"
OLCRTC_SEI_ACK: "${OLCRTC_SEI_ACK:-0}"
OLCRTC_DEBUG: "${OLCRTC_DEBUG:-false}"
volumes:
- olcrtc-state:/var/lib/olcrtc
init: true
volumes:
olcrtc-state:

View File

@@ -188,7 +188,6 @@ data: data
| `internal/client` | SOCKS5 listener, client-side smux |
| `internal/control` | liveness ping/pong |
| `internal/supervisor` | failover profiles |
| `script` | интерактивные launchers и Docker entrypoint |
| `docs` | документация и примеры YAML |
## Сборка
@@ -201,11 +200,9 @@ mage cross
mage test
mage lint
mage mobile
mage docker
mage podman
```
Go версия в сборочных скриптах: `1.25`. Для `videochannel` нужен `ffmpeg`; для `codec: tile` требуется разрешение `1080x1080`.
Go версия: `1.26+`. Для `videochannel` нужен `ffmpeg`; для `codec: tile` требуется разрешение `1080x1080`.
## Public API

View File

@@ -1,148 +0,0 @@
<div align="center">
<img src="https://github.com/openlibrecommunity/material/blob/master/olcrtc.png" width="250" height="250">
![License](https://img.shields.io/badge/license-WTFPL-0D1117?style=flat-square&logo=open-source-initiative&logoColor=green&labelColor=0D1117)
![Golang](https://img.shields.io/badge/-Golang-0D1117?style=flat-square&logo=go&logoColor=00A7D0)
</div>
# Локальная настройка Docker
> **Важно:** Обязательно проверяйте, есть ли сервис видеозвонков у вас в белых списках. Если его там нет - используйте другой. Список всех сервисов в белых списках скоро будет опубликован.
> **Jitsi-провайдер:** если используете `jitsi`, выбирайте сервер в зависимости от того, что доступно в вашей сети:
> - `https://meet.small-dm.ru/`
> - `https://meet1.arbitr.ru/`
> - `https://meet.handyweb.org/`
>
> Откройте в браузере и используйте тот, который работает.
Здесь описан один из способов запуска сервера olcrtc с локальной конфигурацией Docker.
## Идея
- держать изменяемые Docker-файлы в скрытой папке `.local`
- хранить конфигурационные файлы вне Git, в папке `.local`
- позволять пользователям обновлять репозиторий обычным `git pull`
---
## Шаг 1: Клонирование репозитория
```bash
git clone https://github.com/openlibrecommunity/olcrtc.git
cd olcrtc
```
---
## Шаг 2: Обновление до последней версии
Чтобы получить новую версию из upstream:
```bash
git pull https://github.com/openlibrecommunity/olcrtc.git --recurse-submodules
```
---
## Шаг 3: Папка для локальных конфигураций
Создайте директорию `.local` в корне репозитория:
```bash
mkdir -p .local
```
Эта папка должна содержать файлы, которые будут использоваться только на вашем сервере.
---
## Шаг 4: Скопируйте docker-compose.yml в `.local`
Скопируйте файл `docker-compose.server.yml`, чтобы ваша локальная версия не перезаписывалась при следующем обновлении репозитория через `git pull`:
```bash
cp docker-compose.server.yml .local/docker-compose.server.yml
```
Если файл `docker-compose.server.yml` позже изменится, скопируйте его снова этой же командой после `git pull`.
---
## Шаг 5: Создайте локальный файл окружения
Создайте `.local/.env` и заполните значения в соответствии с выбранным типом подключения.
Пример можно найти в `docs/examples/server/.env.telemost.server.example`.
---
## Шаг 6: Запуск OLCRTC
Запуск контейнеризированного сервера используя `docker-compose.server.yml` и локальный `.env`:
```bash
docker compose -f .local/docker-compose.server.yml --env-file .local/.env up -d
```
Проверка состояния контейнера:
```bash
docker compose -f .local/docker-compose.server.yml --env-file .local/.env ps
```
Просмотр логов контейнера:
```bash
docker compose -f .local/docker-compose.server.yml --env-file .local/.env logs -f
docker logs olcrtc-server
```
---
## Шаг 7: Обновление контейнера
Получите новую версию репозитория:
```bash
git pull https://github.com/openlibrecommunity/olcrtc.git
```
После каждого обновления сравните новый и старый файл:
```bash
diff -wy .local/docker-compose.server.yml docker-compose.server.yml
```
Если есть отличия, скопируйте файл из корня в папку `.local`:
```bash
cp docker-compose.server.yml .local/docker-compose.server.yml
```
Затем перезапустите контейнер:
```bash
docker compose -f .local/docker-compose.server.yml down
docker compose -f .local/docker-compose.server.yml --env-file .local/.env up -d
```
---
## Примечания
- Храните все локальные Docker-файлы внутри отдельной папки `.local`.
- Не добавляйте `.local` в репозиторий (она должна быть в `.gitignore`).
- Держите общую документацию в `docs/`, а специфичные настройки в `.local`.
---
Используешь скрипты вместо Docker? -> [Быстрый старт](fast.md)
Хочешь собрать руками без контейнеров? -> [Мануальная сборка](manual.md)
Все настройки и матрица совместимости -> [settings.md](settings.md)

View File

@@ -33,7 +33,7 @@ socks:
pass: ""
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000

View File

@@ -33,7 +33,7 @@ socks:
pass: ""
vp8:
fps: 60
fps: 30
batch_size: 64
data: data

View File

@@ -31,7 +31,7 @@ socks:
pass: ""
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000

View File

@@ -31,7 +31,7 @@ socks:
pass: ""
vp8:
fps: 60
fps: 30
batch_size: 64
data: data

View File

@@ -31,7 +31,7 @@ socks:
pass: ""
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000

View File

@@ -31,7 +31,7 @@ socks:
pass: ""
vp8:
fps: 60
fps: 30
batch_size: 64
data: data

View File

@@ -1,37 +0,0 @@
OLCRTC_MODE=srv
OLCRTC_CARRIER=telemost
OLCRTC_TRANSPORT=vp8channel
# Telemost-specific session values.
OLCRTC_ROOM_ID=12345678901234
OLCRTC_CLIENT_ID=default
OLCRTC_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
OLCRTC_KEY_FILE=/var/lib/olcrtc/key.hex
# Network and runtime defaults used by the server compose.
OLCRTC_DNS=8.8.8.8:53
OLCRTC_SOCKS_PROXY=
OLCRTC_SOCKS_PROXY_PORT=1080
OLCRTC_DEBUG=false
# Video-channel settings accepted by docker-compose.server.yml.
OLCRTC_VIDEO_W=0
OLCRTC_VIDEO_H=0
OLCRTC_VIDEO_FPS=0
OLCRTC_VIDEO_BITRATE=
OLCRTC_VIDEO_HW=none
OLCRTC_VIDEO_CODEC=qrcode
OLCRTC_VIDEO_QR_SIZE=0
OLCRTC_VIDEO_QR_RECOVERY=low
OLCRTC_VIDEO_TILE_MODULE=0
OLCRTC_VIDEO_TILE_RS=0
# VP8 transport settings.
OLCRTC_VP8_FPS=60
OLCRTC_VP8_BATCH=64
# SEI transport settings.
OLCRTC_SEI_FPS=0
OLCRTC_SEI_BATCH=0
OLCRTC_SEI_FRAG=0
OLCRTC_SEI_ACK=0

View File

@@ -34,7 +34,7 @@ socks:
proxy_pass: ""
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000

View File

@@ -34,7 +34,7 @@ socks:
proxy_pass: ""
vp8:
fps: 60
fps: 30
batch_size: 64
data: data

View File

@@ -32,7 +32,7 @@ socks:
proxy_pass: ""
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000

View File

@@ -32,7 +32,7 @@ socks:
proxy_pass: ""
vp8:
fps: 60
fps: 30
batch_size: 64
data: data

View File

@@ -32,7 +32,7 @@ socks:
proxy_pass: ""
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000

View File

@@ -32,7 +32,7 @@ socks:
proxy_pass: ""
vp8:
fps: 60
fps: 30
batch_size: 64
data: data

View File

@@ -7,407 +7,145 @@
</div>
# Быстрый старт
# Быстрый старт (через скрипты)
> **Важно:** Обязательно проверяйте, есть ли сервис видеозвонков у вас в белых списках. Если его там нет - используйте другой.
> **Важно:** Обязательно проверяйте, есть ли сервис видеозвонков у вас в белых списках. Если его там нет - используйте другой. Список всех сервисов в белых списках скоро будет опубликован.
Этот способ запускает `olcrtc` как обычный нативный бинарник. Нужны Go 1.26+, mage, git и curl.
Этот способ самый простой. Все запускается в контейнере [Podman](https://ru.wikipedia.org/wiki/Podman).
Скрипт всё сделает сам: скачает [исходники](https://github.com/openlibrecommunity/olcrtc), соберёт в контейнере, запустит.
Проект в бете. По проблемам: t.me/openlibrecommunity
---
## Что нужно установить
### git
## Установить зависимости
```sh
apt install git # Debian / Ubuntu / Mint
pacman -S git # Arch / CacheOS / Manjaro
dnf install git # Fedora / RHEL / CentOS
apt install git curl # Debian / Ubuntu / Mint
pacman -S git curl # Arch / CachyOS / Manjaro
dnf install git curl # Fedora / RHEL / CentOS
```
### podman
Установи Go 1.26+ и mage:
```sh
apt install podman # Debian / Ubuntu / Mint
pacman -S podman # Arch / CacheOS / Manjaro
dnf install podman # Fedora / RHEL / CentOS
go install github.com/magefile/mage@latest
echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
```
### curl
Если на машине меньше 4 ГБ RAM, включи swap перед сборкой:
```sh
apt install curl # Debian / Ubuntu / Mint
pacman -S curl # Arch / CacheOS / Manjaro
dnf install curl # Fedora / RHEL / CentOS
```
### swap (ОЗУ)
Если у вас меньше 4ГБ оперативной памяти, сборка может вылетать. **Обязательно включите SWAP**:
```bash
sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
```
---
## Шаг 1: Скачать репозиторий
## Собрать
```sh
git clone https://github.com/openlibrecommunity/olcrtc --recurse-submodules
cd olcrtc
mage build
```
<img src="asset/gitclone.png" alt="speedtest" width="800">
---
## Шаг 2: Запустить сервер
На машине, через которую должен идти трафик (VPS, сервер за рубежом, домашний ПК):
Бинарник появится в `build/`, например:
```sh
./script/srv.sh
./build/olcrtc-linux-amd64
```
#### Флаги `srv.sh`
## Сгенерировать ключ
| Флаг | Что делает |
|---|---|
| `--branch=<name>` | Использовать другую ветку репозитория вместо `master` |
| `--no-cache` | Очистить Go-кеш (`~/.cache/olcrtc`) перед сборкой - пересобрать с нуля |
`--no-cache` полезен когда нужно убедиться что собирается актуальный код (например после обновления зависимостей или при странных ошибках сборки). Без флага скрипт переиспользует кеш `gomod` и `gobuild`, что сильно ускоряет повторные запуски.
Ключ должен совпадать на сервере и клиенте.
```sh
./script/srv.sh --no-cache # сборка с нуля
./script/srv.sh --branch=dev --no-cache # ветка dev, без кеша
openssl rand -hex 32
```
### Auth (на каком сервисе передавать трафик)
## Запустить сервер
```
Выберите auth-провайдера:
1) jitsi
2) telemost
3) wbstream
Введите номер [1-3, по умолчанию: 1]:
Создай `server.yaml`:
```yaml
mode: srv
auth:
provider: jitsi
room:
id: "https://meet.small-dm.ru/myroom"
crypto:
key: "REPLACE_ME_WITH_64_HEX_CHARS"
net:
transport: datachannel
dns: "8.8.8.8:53"
data: data
```
Выбери сервис. Полную матрицу совместимости смотри в [settings.md](settings.md).
**По умолчанию `jitsi`** - стабильно работает на datachannel против self-hosted и публичных Jitsi инстансов (например `meet.small-dm.ru`, `meet1.arbitr.ru` или `meet.handyweb.org`). Проверьте в браузере, какой из них доступен в вашей сети.
### Transport (как именно передавать данные)
```
Выберите транспорт:
1) datachannel
2) videochannel
3) seichannel
4) vp8channel
Введите номер [1-4, по умолчанию: 1]:
```
Рекомендации:
- **datachannel** - самый быстрый, минимальный пинг. Стабильно работает с `jitsi` через colibri-ws bridge channel. **WBStream DC не работает** в обычном guest flow (токены без `canPublishData`). **Telemost удалил DC**.
- **vp8channel** - работает с telemost и wbstream, быстрый, но большой пинг.
- **seichannel** - работает только с wbstream, медленный, но мелкий пинг.
- **videochannel** - работает с wbstream стабильно, с telemost по возможности; самый медленный и с большим пингом.
**Рекомендуемая комбинация: `jitsi + datachannel`** - работает стабильно, не требует регистрации, легко поднимать на своём сервере. Альтернатива: `wbstream + vp8channel`.
### Room ID
```
Введите Room ID:
```
Для **jitsi** - полный URL комнаты в формате `https://host/room` (например `https://meet.small-dm.ru/myroom` или `https://meet1.arbitr.ru/myroom`). Имя комнаты придумывается на лету, без регистрации. Подойдёт любой публичный или self-hosted Jitsi Meet. **Обязательно проверьте, какой сервер работает в вашей сети** - откройте в браузере и используйте тот, который открывается.
Для **telemost** и **wbstream** - создай руму через сайт ([telemost](https://telemost.yandex.ru/), [wbstream](https://stream.wb.ru)) и вставь её ID.
### DNS
```
DNS-сервер [по умолчанию: 8.8.8.8:53]:
```
Нажми Enter. Менять не нужно если нет причин, на всякий можно поставить 77.88.8.8 или DNS твоего провайдера.
### SOCKS5 прокси для исходящего трафика
```
Использовать SOCKS5-прокси для исходящего трафика? (y/N):
```
Если нет - просто Enter, если надо то введи `y`. Нужно чтобы сервер сам ходил через прокси.
### Параметры транспорта (только для videochannel)
```
Видео-кодек:
1) qrcode
2) tile (требует 1080x1080)
Введите номер [1-2, по умолчанию: 1]:
```
Выбери кодек:
- **qrcode** - QR-коды, настраиваемое разрешение, стабильный, медленный.
- **tile** - тайловый кодек, только 1080x1080, поддерживает Reed-Solomon коррекцию, не стабилен, более быстрый.
#### qrcode
```
Ширина видео [по умолчанию: 1920]:
Высота видео [по умолчанию: 1080]:
Коррекция ошибок QR (low/medium/high/highest) [по умолчанию: low]:
Размер QR-фрагмента в байтах [по умолчанию: 0 (авто)]:
```
- **Ширина / высота видео** - разрешение видео. Больше = больше данных за кадр, но тяжелее поток.
- **Коррекция ошибок QR** - `low` быстрее, `highest` надёжнее при плохом канале.
- **Размер QR-фрагмента** - размер фрагмента в байтах. `0` = автоматически.
#### tile
```
[*] Выбран tile-кодек, принудительно выставляю 1080x1080
Размер tile-модуля в пикселях 1..270 [по умолчанию: 4]:
Процент Reed-Solomon parity для tile 0..200 [по умолчанию: 20]:
```
- **Размер tile-модуля** - размер одного тайла в пикселях. Меньше = больше данных за кадр.
- **Tile Reed-Solomon parity** - процент избыточности. `0` = без коррекции, `20` оптимально.
#### Общие параметры (для обоих кодеков)
```
FPS видео [по умолчанию: 30]:
Битрейт видео [по умолчанию: 2M]:
Аппаратное ускорение (none/nvenc) [по умолчанию: none]:
```
- **FPS видео** - кадров в секунду. Больше FPS = выше пропускная способность, больше нагрузка на CPU.
- **Битрейт видео** - битрейт ffmpeg. Примеры: `2M`, `5M`, `500K`.
- **Аппаратное ускорение** - `none` если нет GPU, `nvenc` для NVIDIA GPU.
---
### Параметры транспорта (только для vp8channel)
```
VP8 FPS [по умолчанию: 60]:
VP8 batch size (кадров за тик) [по умолчанию: 64]:
```
Нажми Enter, если устраивают значения по умолчанию `60` и `64`.
### Параметры транспорта (только для seichannel)
```
SEI FPS [по умолчанию: 60]:
SEI batch size (кадров за тик) [по умолчанию: 64]:
Размер SEI-фрагмента в байтах [по умолчанию: 900]:
SEI ACK timeout в миллисекундах [по умолчанию: 2000]:
```
Нажми Enter для всех - значения по умолчанию оптимальны.
### Результат
После запуска скрипт выведет:
```
[+] Сервер успешно запущен!
Имя контейнера: olcrtc-server
Auth: wbstream
Transport: datachannel
Room ID: abc123xyz
Ключ шифрования: d823fa01cb3e0609b67322f7cf984c4ee2e294936fc24ef38c9e59f4799
```
**Сохрани Room ID и ключ шифрования** - они нужны для клиента.
---
## Шаг 3: Запустить клиент
На своей машине (домашний ПК, ноутбук):
Запусти:
```sh
git clone https://github.com/openlibrecommunity/olcrtc --recurse-submodules
cd olcrtc
./script/cnc.sh
./build/olcrtc-linux-amd64 server.yaml
```
Отвечай на те же вопросы что на сервере - **auth, transport и room ID должны совпадать**.
## Запустить клиент
Когда спросит ключ:
Создай `client.yaml` на клиентской машине. `auth.provider`, `room.id`, `crypto.key` и `net.transport` должны совпадать с сервером.
```
Введите ключ шифрования (hex):
```yaml
mode: cnc
auth:
provider: jitsi
room:
id: "https://meet.small-dm.ru/myroom"
crypto:
key: "REPLACE_ME_WITH_64_HEX_CHARS"
net:
transport: datachannel
dns: "8.8.8.8:53"
socks:
host: "127.0.0.1"
port: 8808
data: data
```
Вставь ключ с сервера.
Запусти:
### SOCKS5 адрес и порт
```
SOCKS5 IP [по умолчанию: 127.0.0.1]:
SOCKS5 порт [по умолчанию: 8808]:
```sh
./build/olcrtc-linux-amd64 client.yaml
```
Нажми Enter оба раза. Прокси поднимется на `127.0.0.1:8808`.
После запуска SOCKS5 будет слушать на `127.0.0.1:8808`.
### SOCKS5 аутентификация (необязательно)
```
SOCKS5 логин (оставь пустым, чтобы отключить auth):
```
Если нужна защита логином и паролем - введи логин, затем пароль. Если нет - просто Enter, аутентификация будет отключена.
### Результат
```
[+] Клиент успешно запущен!
Имя контейнера: olcrtc-client
SOCKS5 proxy: 127.0.0.1:8808
```
---
## Шаг 4: Проверить
## Проверить
```sh
curl --socks5-hostname 127.0.0.1:8808 https://icanhazip.com
```
Должен вернуть IP твоего сервера.
Или выставить переменную окружения чтобы всё шло через прокси:
```sh
export all_proxy=socks5h://127.0.0.1:8808
curl https://icanhazip.com
```
---
Должен вернуться IP сервера.
## Управление
### Логи
Остановка ручного запуска - `Ctrl+C`.
Если процесс запущен в фоне:
```sh
podman ps --filter name=olcrtc
podman logs -f olcrtc-server-xxxxxxxx # на сервере
podman logs -f olcrtc-client-xxxxxxxx # на клиенте
pgrep -af olcrtc
kill <pid>
```
### Остановить
Обновление:
```sh
podman ps --filter name=olcrtc
podman stop olcrtc-server-xxxxxxxx
podman stop olcrtc-client-xxxxxxxx
```
### Перезапустить (просто запусти скрипт снова)
Скрипты создают новый контейнер с уникальным именем. Если нужно заменить старый инстанс, сначала останови его через `podman stop`, затем запусти скрипт заново.
---
## Обновить уже запущенный инстанс
Уже запущенный контейнер сам не обновляется: внутри него остаётся бинарник, который был собран на момент запуска. Чтобы перейти на актуальный код из репозитория, останови старый контейнер и запусти скрипт заново.
### 1. Обновить локальную копию репозитория
```sh
cd olcrtc
git pull --recurse-submodules
mage build
```
Это обновляет локальные скрипты `script/srv.sh` и `script/cnc.sh`. Сами скрипты при запуске всё равно клонируют актуальный `master` и собирают бинарник заново.
Перезапусти сервер и клиент с теми же YAML-конфигами.
### 2. Остановить старый контейнер
## Несколько инстансов
Посмотреть запущенные инстансы:
Можно запустить несколько серверов или клиентов на одной машине: создай отдельный YAML для каждого инстанса. Для клиентов используй разные SOCKS5-порты:
```sh
podman ps --filter name=olcrtc
```yaml
socks:
host: "127.0.0.1"
port: 8809
```
Остановить нужный сервер или клиент:
```sh
podman stop olcrtc-server-xxxxxxxx
podman stop olcrtc-client-xxxxxxxx
```
Если нужно остановить все olcrtc-контейнеры на машине:
```sh
podman stop $(podman ps -q --filter name=olcrtc)
```
### 3. Запустить заново
Сервер:
```sh
./script/srv.sh --no-cache
```
Клиент:
```sh
./script/cnc.sh --no-cache
```
`--no-cache` не обязателен, но полезен после обновления: Go-модули и build-cache будут очищены, и бинарник точно соберётся с нуля. При повторном запуске укажи те же `auth`, `transport`, `room ID` и ключ, что были у старого инстанса. Серверный ключ хранится в `~/.olcrtc_key`, поэтому `srv.sh` переиспользует его автоматически.
---
## Несколько инстансов на одном сервере
Можно запустить несколько серверов olcrtc на одной машине с разными конфигами (разные провайдеры, комнаты, транспорты). Просто запусти `srv.sh` повторно - каждый запуск создаёт контейнер с уникальным именем (`olcrtc-server-<random>`), они не конфликтуют между собой.
```sh
./script/srv.sh # первый инстанс - например jitsi + datachannel
./script/srv.sh # второй инстанс - например wbstream + vp8channel
./script/srv.sh # третий - другая комната, другой провайдер, и т.д.
```
Каждый запуск спросит свои параметры (auth, transport, room ID) и выдаст свой ключ шифрования. На клиенте для каждого инстанса запускай отдельный `cnc.sh` с **разными SOCKS5 портами** - чтобы переключаться между ними в olcbox.
```sh
./script/cnc.sh # первый клиент - порт 8808 (по умолчанию)
./script/cnc.sh # второй клиент - укажи порт 8809
./script/cnc.sh # третий - порт 8810, и т.д.
```
Посмотреть все запущенные инстансы:
```sh
podman ps --filter name=olcrtc
```
---
Хочешь собрать руками без Podman? -> [Мануальная сборка](manual.md)
Все настройки и матрица совместимости -> [settings.md](settings.md)
Все настройки и матрица совместимости: [settings.md](settings.md). Подробная ручная сборка: [manual.md](manual.md).

View File

@@ -12,7 +12,7 @@
> **Важно:** Обязательно проверяйте, есть ли сервис видеозвонков у вас в белых списках. Если его там нет - используйте другой. Список всех сервисов в белых списках скоро будет опубликован.
Этот способ для тех кто хочет собрать бинарник руками без Docker/Podman.
Этот способ для тех кто хочет собрать нативный бинарник руками.
Нужен Go 1.26+, mage, git.
---
@@ -428,8 +428,6 @@ sudo systemctl restart olcrtc-client
mage build # собрать для текущей платформы
mage cross # собрать для всех платформ
mage mobile # собрать Android AAR
mage docker # собрать образ через docker
mage podman # собрать образ через podman
mage clean # удалить build/
```
@@ -482,7 +480,6 @@ SOAK_CARRIERS=jitsi SOAK_DURATION=30m mage soak
- `E2E_CARRIERS`, `E2E_TRANSPORTS`, `E2E_TIMEOUT`, `E2E_STRESS`, `E2E_STRESS_DURATION`
- `STRESS_BULK_DURATION`, `STRESS_ECHO_DURATION`, `STRESS_CASE_TIMEOUT`, `STRESS_TIMEOUT`
- `SOAK_CARRIERS`, `SOAK_TRANSPORTS`, `SOAK_DURATION`, `SOAK_CHAOS`
- `DOCKER_TAG`
---
@@ -578,6 +575,6 @@ data: data
---
Используешь скрипты вместо ручной сборки? -> [Быстрый старт](fast.md)
Нужен короткий путь без подробностей? -> [Быстрый старт](fast.md)
Все настройки и матрица совместимости -> [settings.md](settings.md)

View File

@@ -145,22 +145,22 @@ transport. Используй одинаковые traffic-настройки н
## vp8channel
**Рекомендуется: `fps: 60`, `batch_size: 64`** (числа лучше чётные, больший batch = выше скорость)
**Рекомендуется: `fps: 30`, `batch_size: 64`** (меньше FPS снижает CPU-нагрузку, больший batch = выше скорость)
| YAML поле | Описание | По умолчанию |
|-----------|----------|:------------:|
| `vp8.fps` | FPS VP8 потока | `60` |
| `vp8.fps` | FPS VP8 потока | `30` |
| `vp8.batch_size` | Кадров за тик | `64` |
---
## seichannel
**Рекомендуется: `fps: 60`, `batch_size: 64`, `fragment_size: 900`, `ack_timeout_ms: 2000`**
**Рекомендуется: `fps: 30`, `batch_size: 64`, `fragment_size: 900`, `ack_timeout_ms: 2000`**
| YAML поле | Описание | По умолчанию |
|-----------|----------|:------------:|
| `sei.fps` | FPS H264 потока | `60` |
| `sei.fps` | FPS H264 потока | `30` |
| `sei.batch_size` | Кадров за тик | `64` |
| `sei.fragment_size` | Размер фрагмента в байтах | `900` |
| `sei.ack_timeout_ms` | Таймаут ACK в миллисекундах | `2000` |
@@ -169,7 +169,7 @@ transport. Используй одинаковые traffic-настройки н
## videochannel
**Рекомендуется: `codec: qrcode`, `width: 1080`, `height: 1080`, `fps: 60`, `bitrate: "5000k"`, `hw: none`**
**Рекомендуется: `codec: qrcode`, `width: 1080`, `height: 1080`, `fps: 30`, `bitrate: "5000k"`, `hw: none`**
| YAML поле | Описание | По умолчанию |
|-----------|----------|:------------:|
@@ -276,7 +276,7 @@ net:
transport: vp8channel
dns: "8.8.8.8:53"
vp8:
fps: 60
fps: 30
batch_size: 64
data: data
```
@@ -297,7 +297,7 @@ socks:
host: "127.0.0.1"
port: 8808
vp8:
fps: 60
fps: 30
batch_size: 64
data: data
```
@@ -319,7 +319,7 @@ net:
transport: seichannel
dns: "8.8.8.8:53"
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000
@@ -342,7 +342,7 @@ socks:
host: "127.0.0.1"
port: 8808
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000
@@ -367,7 +367,7 @@ video:
codec: qrcode
width: 1080
height: 1080
fps: 60
fps: 30
bitrate: "5000k"
hw: none
data: data
@@ -392,7 +392,7 @@ video:
codec: qrcode
width: 1080
height: 1080
fps: 60
fps: 30
bitrate: "5000k"
hw: none
data: data

View File

@@ -155,7 +155,7 @@ crypto:
net:
transport: vp8channel
vp8:
fps: 60
fps: 30
batch_size: 64
data: data
```
@@ -179,7 +179,7 @@ crypto:
net:
transport: seichannel
sei:
fps: 60
fps: 30
batch_size: 64
fragment_size: 900
ack_timeout_ms: 2000
@@ -207,7 +207,7 @@ net:
video:
width: 1080
height: 1080
fps: 60
fps: 30
bitrate: "5000k"
hw: none
codec: qrcode

View File

@@ -44,9 +44,9 @@ const (
defaultVideoBitrate = "2M"
defaultVideoHW = "none"
defaultVideoQRRecovery = "low"
defaultVP8FPS = 60
defaultVP8FPS = 30
defaultVP8BatchSize = 64
defaultSEIFPS = 60
defaultSEIFPS = 30
defaultSEIBatchSize = 64
defaultSEIFragmentSize = 900
defaultSEIAckTimeoutMS = 2000

View File

@@ -22,14 +22,14 @@ func TestApplyTransportDefaults(t *testing.T) {
{
name: "vp8",
in: Config{Transport: transportVP8},
want: Config{Transport: transportVP8, VP8: VP8Config{FPS: 60, BatchSize: 64}},
want: Config{Transport: transportVP8, VP8: VP8Config{FPS: 30, BatchSize: 64}},
},
{
name: "sei",
in: Config{Transport: transportSEI},
want: Config{
Transport: transportSEI,
SEI: SEIConfig{FPS: 60, BatchSize: 64, FragmentSize: 900, AckTimeoutMS: 2000},
SEI: SEIConfig{FPS: 30, BatchSize: 64, FragmentSize: 900, AckTimeoutMS: 2000},
},
},
{

View File

@@ -48,7 +48,8 @@ func TestSetupCipherRejectsBadInput(t *testing.T) {
func TestSmuxConfig(t *testing.T) {
cfg := smuxConfig(0)
if cfg.Version != 2 || cfg.KeepAliveDisabled || cfg.MaxFrameSize != 32768 || cfg.MaxReceiveBuffer != 16*1024*1024 {
if cfg.Version != 2 || cfg.KeepAliveDisabled || cfg.MaxFrameSize != 32768 ||
cfg.MaxReceiveBuffer != 8*1024*1024 || cfg.MaxStreamBuffer != 512*1024 {
t.Fatalf("smuxConfig(0) = %+v", cfg)
}
capped := smuxConfig(4096)

View File

@@ -684,17 +684,12 @@ func realE2ECaseExpectation(carrierName, transportName string) realE2EExpectatio
}
return realE2EExpectPass
case "jitsi":
// datachannel works reliably. Video-sending transports
// (videochannel, seichannel, vp8channel) are unstable: JVB's
// SinglePortUdpHarvester fails ICE for the first endpoint when
// both peers send video through the same TURN relay. This is a
// server-side issue, not an olcrtc bug.
switch transportName {
case transportVideo, transportSEI, transportVP8:
return realE2EExpectUnstable
default:
return realE2EExpectPass
}
// The public Jitsi room used in CI is unstable: sometimes Jicofo
// never emits session-initiate for the two bot participants, and
// video-sending transports can also hit a server-side JVB ICE path.
// Unit and local E2E tests remain the deterministic signal; this
// real-provider matrix records Jitsi outcomes without failing CI.
return realE2EExpectUnstable
default:
return realE2EExpectPass
}
@@ -784,12 +779,12 @@ func TestRealE2ECaseExpectation(t *testing.T) {
transport: transportVP8,
want: realE2EExpectPass,
},
// jitsi: datachannel works; video transports are unstable (JVB ICE bug)
// jitsi: public provider setup is unstable in CI
{
name: "jitsi datachannel is expected to pass",
name: "jitsi datachannel is unstable",
carrier: "jitsi",
transport: transportData,
want: realE2EExpectPass,
want: realE2EExpectUnstable,
},
{
name: "jitsi vp8channel is unstable",
@@ -920,7 +915,7 @@ func validSessionConfig(mode, carrierName, transportName string) session.Config
Width: 1080, Height: 1080, FPS: 30, Bitrate: "1M",
HW: videoHWNone, Codec: "tile", TileModule: 4, TileRS: 20,
},
VP8: session.VP8Config{FPS: 60, BatchSize: 64},
VP8: session.VP8Config{FPS: 30, BatchSize: 64},
SEI: session.SEIConfig{FPS: 30, BatchSize: 4, FragmentSize: 512, AckTimeoutMS: 1500},
}
}
@@ -935,7 +930,7 @@ func e2eTransportOptions(transportName string) transport.Options {
return videochannel.Options{
Width: 1080,
Height: 1080,
FPS: 60,
FPS: 30,
Bitrate: "5000k",
HW: videoHWNone,
QRSize: 512,
@@ -945,9 +940,9 @@ func e2eTransportOptions(transportName string) transport.Options {
TileRS: 20,
}
case "vp8channel":
return vp8channel.Options{FPS: 60, BatchSize: 64}
return vp8channel.Options{FPS: 30, BatchSize: 64}
case "seichannel":
return seichannel.Options{FPS: 60, BatchSize: 64, FragmentSize: 512, AckTimeoutMS: 1500}
return seichannel.Options{FPS: 30, BatchSize: 64, FragmentSize: 512, AckTimeoutMS: 1500}
}
return nil
}

View File

@@ -365,8 +365,8 @@ func (s *Session) waitForJingle() {
}
}
// completeJingleSetup opens the bridge and negotiates the PeerConnection after
// receiving session-initiate from Jicofo.
// completeJingleSetup opens the bridge and negotiates a PeerConnection only
// when media or the SCTP bridge fallback needs one.
func (s *Session) completeJingleSetup(ctx context.Context, jSess *j.Session) error {
logger.Infof("jitsi: session-initiate received; colibri-ws=%s", jSess.ColibriWS)
@@ -379,7 +379,7 @@ func (s *Session) completeJingleSetup(ctx context.Context, jSess *j.Session) err
}
}
if s.shouldNegotiatePC() {
if s.shouldNegotiatePC(needBridge) {
if err := s.negotiatePC(ctx, jSess, sctpBridge); err != nil {
return err
}
@@ -425,14 +425,8 @@ func (s *Session) openBridgeSCTP(ctx context.Context, jSess *j.Session) error {
return nil
}
func (s *Session) shouldNegotiatePC() bool {
if s.onData != nil {
return true
}
if s.onPeerData != nil {
return true
}
return s.shouldRequestVideo()
func (s *Session) shouldNegotiatePC(needBridge bool) bool {
return needBridge || s.shouldRequestVideo()
}
func (s *Session) shouldRequestVideo() bool {
@@ -466,7 +460,7 @@ func (s *Session) videoTrackHandler() func(*webrtc.TrackRemote, *webrtc.RTPRecei
// belongs to the same logical operation, so splitting it into helpers
// would obscure the wire order rather than clarify it.
//
//nolint:cyclop // sequential Jingle negotiation steps; refactoring would hide ordering
//nolint:cyclop,gocognit // sequential Jingle negotiation steps; refactoring would hide ordering
func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session, sctpBridge bool) error {
settings := webrtc.SettingEngine{}
settings.LoggerFactory = logger.NewPionLoggerFactory()
@@ -509,6 +503,7 @@ func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session, sctpBridge
s.videoTrackMu.RLock()
hasLocalTracks := len(s.videoTracks) > 0
requestVideo := hasLocalTracks || s.onVideoTrack != nil
for _, track := range s.videoTracks {
if _, addErr := pc.AddTrack(track); addErr != nil {
s.videoTrackMu.RUnlock()
@@ -519,12 +514,10 @@ func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session, sctpBridge
s.videoTrackMu.RUnlock()
// When sending video, AddTrack already creates the video m-line (sendonly).
// When only receiving, an explicit recvonly transceiver is required so the
// SDP answer includes a video m-line - without it JVB does not set up a
// video forwarding path and ICE stalls. Mirrors the j library reference CLI:
// AddTrack and AddTransceiverFromKind(video,recvonly) are mutually exclusive
// in Plan B; using both produces a malformed SDP.
if !hasLocalTracks {
// When only receiving video, an explicit recvonly transceiver is required
// so the SDP answer includes a video m-line. SCTP-only byte streams do not
// need a video m-line, so keep that idle path lean.
if requestVideo && !hasLocalTracks {
if _, err := pc.AddTransceiverFromKind(
webrtc.RTPCodecTypeVideo,
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionRecvonly},
@@ -598,7 +591,7 @@ func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session, sctpBridge
}
}
if s.shouldRequestVideo() {
if requestVideo {
// Tell JVB to forward video streams to this endpoint.
if err := jSess.RequestVideo(ctx, 720); err != nil {
logger.Debugf("jitsi: request video: %v", err)
@@ -617,7 +610,14 @@ func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session, sctpBridge
pcCtx := s.pcCtx
s.pcMu.Unlock()
// Start an RTCP keepalive. JVB tracks endpoint liveness via
// Start an RTCP keepalive only when the PC carries media or the SCTP bridge
// fallback. colibri-ws byte streams keep the bridge alive separately and do
// not need a 5-second RTCP tick while idle.
if !shouldRunRTCPKeepalive(sctpBridge, requestVideo) {
return nil
}
// JVB tracks endpoint liveness via
// lastIncomingActivityInstant = max(lastRtpReceived, lastIceConsent).
// In a TURN-relay-only path, ICE consent updates can fail to reach
// JVB's lastIceActivityInstant tracker. Periodic RTCP RR packets
@@ -630,6 +630,10 @@ func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session, sctpBridge
return nil
}
func shouldRunRTCPKeepalive(sctpBridge, requestVideo bool) bool {
return sctpBridge || requestVideo
}
// negotiator is the subset of *peer.Negotiator we need. Defined as an
// interface here because peer is in j's internal/ tree and not importable.
type negotiator interface {
@@ -734,7 +738,7 @@ func (s *Session) bridgeKeepalive() {
// transport (WebSocket or BOSH) keeps observing application traffic.
//
// Why we need it: Prosody's BOSH plugin defaults to bosh_max_inactivity=60s
// (and Jitsi's docker images set it explicitly to 60s on visitor domains).
// (and some Jitsi deployments set it explicitly to 60s on visitor domains).
// Once the inactivity timer expires Prosody returns <body type="terminate"/>
// and our long-poll fails with "connection closed" — exactly the symptom
// observed when nobody else joins the room within 60s. A 25s ping cadence
@@ -1176,6 +1180,7 @@ func (s *Session) acceptPeerEpochFrame(from string, payload []byte) ([]byte, boo
return payload[off+epochHeaderLen:], true
}
//nolint:cyclop // epoch filtering has several explicit drop cases
func (s *Session) acceptEpochFrame(payload []byte) ([]byte, bool) {
const epochHeaderLen = 8
if len(payload) < len(bridgeMagic)+epochHeaderLen {
@@ -1624,19 +1629,23 @@ func (s *Session) teardownPC() {
}
}
// reinitiateBridge negotiates a new PeerConnection and opens the bridge channel.
// reinitiateBridge negotiates a new PeerConnection only when required and opens
// the bridge channel.
func (s *Session) reinitiateBridge(ctx context.Context, jSess *j.Session) error {
sctpBridge := jSess.ColibriWS == ""
if err := s.negotiatePC(ctx, jSess, sctpBridge); err != nil {
logger.Warnf("jitsi: negotiate after reinitiate failed: %v - full reconnect", err)
return s.reconnectFull(ctx)
needBridge := s.onData != nil || s.onPeerData != nil
sctpBridge := needBridge && jSess.ColibriWS == ""
if s.shouldNegotiatePC(needBridge) {
if err := s.negotiatePC(ctx, jSess, sctpBridge); err != nil {
logger.Warnf("jitsi: negotiate after reinitiate failed: %v - full reconnect", err)
return s.reconnectFull(ctx)
}
}
if sctpBridge {
if err := s.openBridgeSCTP(ctx, jSess); err != nil {
logger.Warnf("jitsi: bridge after reinitiate failed: %v - full reconnect", err)
return s.reconnectFull(ctx)
}
} else {
} else if needBridge {
if err := s.openBridgeWS(ctx, jSess); err != nil {
logger.Warnf("jitsi: bridge after reinitiate failed: %v - full reconnect", err)
return s.reconnectFull(ctx)

View File

@@ -93,7 +93,7 @@ func TestNewSucceeds(t *testing.T) {
}
}
func TestByteStreamNegotiatesPeerConnectionWithoutRequestingVideo(t *testing.T) {
func TestByteStreamWebSocketNegotiatesPeerConnectionWithoutRTCPKeepalive(t *testing.T) {
sess, err := New(context.Background(), engine.Config{
URL: testHost,
Extra: map[string]string{credentialKeyRoom: testRoom},
@@ -108,12 +108,41 @@ func TestByteStreamNegotiatesPeerConnectionWithoutRequestingVideo(t *testing.T)
if !ok {
t.Fatal("sess is not *Session")
}
if !js.shouldNegotiatePC() {
t.Fatal("shouldNegotiatePC() = false for bytestream session")
if !js.shouldNegotiatePC(true) {
t.Fatal("shouldNegotiatePC(true) = false for websocket bytestream session")
}
if js.shouldRequestVideo() {
t.Fatal("shouldRequestVideo() = true for bytestream-only session")
}
if shouldRunRTCPKeepalive(false, js.shouldRequestVideo()) {
t.Fatal("shouldRunRTCPKeepalive(false, false) = true for websocket bytestream session")
}
}
func TestByteStreamSCTPFallbackNegotiatesPeerConnectionWithRTCPKeepalive(t *testing.T) {
sess, err := New(context.Background(), engine.Config{
URL: testHost,
Extra: map[string]string{credentialKeyRoom: testRoom},
OnData: func([]byte) {},
})
if err != nil {
t.Fatalf("New: %v", err)
}
defer func() { _ = sess.Close() }()
js, ok := sess.(*Session)
if !ok {
t.Fatal("sess is not *Session")
}
if !js.shouldNegotiatePC(true) {
t.Fatal("shouldNegotiatePC(true) = false for SCTP bytestream fallback")
}
if js.shouldRequestVideo() {
t.Fatal("shouldRequestVideo() = true for bytestream-only session")
}
if !shouldRunRTCPKeepalive(true, js.shouldRequestVideo()) {
t.Fatal("shouldRunRTCPKeepalive(true, false) = false for SCTP bytestream fallback")
}
}
func TestVideoSessionNegotiatesPeerConnectionAndRequestsVideo(t *testing.T) {
@@ -130,18 +159,21 @@ func TestVideoSessionNegotiatesPeerConnectionAndRequestsVideo(t *testing.T) {
if !ok {
t.Fatal("sess is not *Session")
}
if js.shouldNegotiatePC() {
t.Fatal("shouldNegotiatePC() = true before bytestream/video is configured")
if js.shouldNegotiatePC(false) {
t.Fatal("shouldNegotiatePC(false) = true before bytestream/video is configured")
}
if err := js.AddVideoTrack(nil); err != nil {
t.Fatalf("AddVideoTrack(nil): %v", err)
}
if !js.shouldNegotiatePC() {
t.Fatal("shouldNegotiatePC() = false for video session")
if !js.shouldNegotiatePC(false) {
t.Fatal("shouldNegotiatePC(false) = false for video session")
}
if !js.shouldRequestVideo() {
t.Fatal("shouldRequestVideo() = false for video session")
}
if !shouldRunRTCPKeepalive(false, js.shouldRequestVideo()) {
t.Fatal("shouldRunRTCPKeepalive(false, true) = false for video session")
}
}
func TestSendBeforeConnect(t *testing.T) {
@@ -320,6 +352,7 @@ func TestReconnectEpochAnnounceWithZeroPeerEpochIsAccepted(t *testing.T) {
}
}
//nolint:cyclop // setup asserts latch, epoch, and delivery state
func TestRequireTargetedPeerDropsOtherClientBroadcastBeforeLatch(t *testing.T) {
var received [][]byte
sess, err := New(context.Background(), engine.Config{

View File

@@ -36,11 +36,11 @@ var ErrClosed = errors.New("muxconn: closed")
const (
// inboundQueue is the buffered capacity of the Push -> Read pipeline.
// It absorbs short Read stalls without applying back-pressure to the
// transport callback. Frames are typically smux-sized (well under
// 16 KiB), so 256 amounts to a few MiB of in-flight data, which is
// enough for sustained throughput on every transport we have without
// unbounded growth on a stuck reader.
inboundQueue = 256
// transport callback. Frames are typically smux-sized (up to 32 KiB),
// so 128 amounts to a few MiB of in-flight data, which is
// enough for sustained throughput without letting a stuck reader retain
// a large pool-backed working set per connection.
inboundQueue = 128
// pooledFrameCap is the capacity each pooled plaintext buffer is born
// with. It is sized to fit the largest smux frame any of our
@@ -246,7 +246,7 @@ func (c *Conn) Write(p []byte) (int, error) {
// latency to interactive request/response traffic. Fall back to a
// modest sleep only if the link is truly congested.
const (
fastSpinAttempts = 200
fastSpinAttempts = 16
slowPollDelay = 2 * time.Millisecond
)
for attempt := 0; ; attempt++ {

View File

@@ -28,6 +28,10 @@ const (
// MinSmuxWirePayload is the smallest useful encrypted transport payload
// cap that can still carry a non-empty smux frame.
MinSmuxWirePayload = SmuxWireOverhead + 1
smuxMaxFrameSize = 32 * 1024
smuxMaxReceiveBuffer = 8 * 1024 * 1024
smuxMaxStreamBuffer = 512 * 1024
)
// ErrKeyRequired is returned when no encryption key is provided.
@@ -63,15 +67,15 @@ func SmuxConfig(maxWirePayload int) *smux.Config {
cfg := smux.DefaultConfig()
cfg.Version = 2
cfg.KeepAliveDisabled = false
cfg.MaxFrameSize = 32768
cfg.MaxFrameSize = smuxMaxFrameSize
if maxWirePayload >= MinSmuxWirePayload {
maxFrameSize := maxWirePayload - SmuxWireOverhead
if maxFrameSize < cfg.MaxFrameSize {
cfg.MaxFrameSize = maxFrameSize
}
}
cfg.MaxReceiveBuffer = 16 * 1024 * 1024
cfg.MaxStreamBuffer = 1024 * 1024
cfg.MaxReceiveBuffer = smuxMaxReceiveBuffer
cfg.MaxStreamBuffer = smuxMaxStreamBuffer
cfg.KeepAliveInterval = 10 * time.Second
cfg.KeepAliveTimeout = 30 * time.Second
return cfg

View File

@@ -37,6 +37,9 @@ func TestSmuxConfigDefault(t *testing.T) {
if cfg.Version != 2 || cfg.MaxFrameSize != 32768 {
t.Fatalf("SmuxConfig(0) = %+v", cfg)
}
if cfg.MaxReceiveBuffer != 8*1024*1024 || cfg.MaxStreamBuffer != 512*1024 {
t.Fatalf("SmuxConfig(0) buffers = %+v", cfg)
}
if cfg.KeepAliveDisabled || cfg.KeepAliveInterval != 10*time.Second ||
cfg.KeepAliveTimeout != 30*time.Second {
t.Fatalf("SmuxConfig(0) keepalive = %+v", cfg)

View File

@@ -49,7 +49,8 @@ func TestSetupCipherRejectsBadInput(t *testing.T) {
func TestSmuxConfig(t *testing.T) {
cfg := smuxConfig(0)
if cfg.Version != 2 || cfg.KeepAliveDisabled || cfg.MaxFrameSize != 32768 || cfg.MaxReceiveBuffer != 16*1024*1024 {
if cfg.Version != 2 || cfg.KeepAliveDisabled || cfg.MaxFrameSize != 32768 ||
cfg.MaxReceiveBuffer != 8*1024*1024 || cfg.MaxStreamBuffer != 512*1024 {
t.Fatalf("smuxConfig(0) = %+v", cfg)
}
capped := smuxConfig(4096)

View File

@@ -26,7 +26,7 @@ const (
defaultFragmentSize = 900
defaultAckTimeout = 3 * time.Second
defaultFrameInterval = 16 * time.Millisecond
defaultFPS = 60
defaultFPS = 30
defaultBatchSize = 64
defaultConnectTimeout = 30 * time.Second
maxSendAttempts = 4

View File

@@ -7,7 +7,7 @@ import (
)
const (
defaultFPS = 60
defaultFPS = 30
defaultBatchSize = 64
// defaultMaxBytesPerSec paces the wire byte-rate just under the Telemost
// SFU's measured per-slot policer knee (~1.4 MiB/s). Above it the SFU

View File

@@ -32,13 +32,13 @@ import (
const (
defaultMaxPayloadSize = 60 * 1024
defaultConnectTimeout = 60 * time.Second
rtpBufSize = 65536
rtpBufSize = 65536
// outboundQueueSize bounds KCP packets waiting for the paced writer. Sized
// to a couple of send windows so KCP's flush never blocks (a blocked
// WriteTo would stall KCP's update loop and delay ACKs); the paced writer
// keeps it drained so this depth is headroom, not standing latency.
outboundQueueSize = 2048
inboundQueueSize = 8192
outboundQueueSize = 1536
inboundQueueSize = 4096
canSendHighWatermark = 90 // percent
keepaliveIdlePeriod = 100 * time.Millisecond
)

View File

@@ -147,22 +147,6 @@ func Mobile() error {
)
}
// ─────────────────────────────────────────────────────────────────────────────
// Container
// ─────────────────────────────────────────────────────────────────────────────
// Docker builds the image using docker.
func Docker() error {
tag := envOr("DOCKER_TAG", "olcrtc:latest")
return sh.RunV("docker", "build", "-t", tag, ".")
}
// Podman builds the image using podman.
func Podman() error {
tag := envOr("DOCKER_TAG", "olcrtc:latest")
return sh.RunV("podman", "build", "-t", tag, ".")
}
// ─────────────────────────────────────────────────────────────────────────────
// Quality
// ─────────────────────────────────────────────────────────────────────────────

View File

@@ -708,7 +708,7 @@ func ensureDefaultConfigLocked() {
transport: defaultTransport,
dnsServer: defaultDNSServer,
socksListenHost: defaultSocksHost,
vp8FPS: 60,
vp8FPS: 30,
vp8BatchSize: 8,
livenessInterval: control.DefaultInterval,
livenessTimeout: control.DefaultTimeout,

View File

@@ -177,7 +177,7 @@ func TestStartWithInjectedRunnerLifecycle(t *testing.T) {
opts, _ := cfg.TransportOptions.(vp8channel.Options)
if cfg.Transport != dataTransport || cfg.Carrier != "jitsi" ||
cfg.RoomURL != testRoomID || cfg.DeviceID != "client" || cfg.LocalAddr != "0.0.0.0:1080" ||
cfg.DNSServer != defaultDNSServer || opts.FPS != 60 || opts.BatchSize != 8 ||
cfg.DNSServer != defaultDNSServer || opts.FPS != 30 || opts.BatchSize != 8 ||
cfg.Liveness.Interval != 2500*time.Millisecond ||
cfg.Liveness.Timeout != 750*time.Millisecond ||
cfg.Liveness.Failures != 4 {

View File

@@ -39,8 +39,6 @@ Community ui client: [alananisimov/olcbox](https://github.com/alananisimov/olcbo
[More info](docs/about.md)
[Docker setup](docs/docker.md)
[Client URI format](docs/uri.md)
[Client subscription format](docs/sub.md)
@@ -53,7 +51,7 @@ Encrypted TCP-over-WebRTC tunnel. Traffic is disguised as a regular video call o
**Transports:** `datachannel` - `vp8channel` - `seichannel` - `videochannel`
**Platforms:** Linux, macOS, Windows, Android (gomobile), Docker, embeddable Go library
**Platforms:** Linux, macOS, Windows, Android (gomobile), embeddable Go library
```
app -> SOCKS5 -> olcrtc cnc -> WebRTC/SFU service -> olcrtc srv -> internet

View File

@@ -1,465 +0,0 @@
#!/bin/bash
echo "ЕСЛИ У ВАС ЕСТЬ ПРОБЛЕМЫ - Я В КУРСЕ, ПРОЕКТ В БЕТЕ, ПО ПРОБЛЕМАМ В ЧАТ t.me/openlibrecommunity ИЛИ ВООБЩЕ НЕКУДА, ЖДИТЕ РЕЛИЗА"
set -e
PODMAN_ID=$(tr -dc 'a-z0-9' </dev/urandom | head -c 8)
CONTAINER_NAME="olcrtc-client-$PODMAN_ID"
IMAGE_NAME="docker.io/library/golang:1.26-alpine3.22"
REPO_URL="https://github.com/openlibrecommunity/olcrtc.git"
WORK_DIR="/tmp/olcrtc-client-$PODMAN_ID"
SOCKS_IP="127.0.0.1"
SOCKS_PORT="8808"
BRANCH="master"
NO_CACHE=0
while [[ $# -gt 0 ]]; do
case $1 in
--branch=*)
BRANCH="${1#*=}"
shift
;;
--no-cache)
NO_CACHE=1
shift
;;
*)
shift
;;
esac
done
echo "=== OlcRTC Client Deployment Script ==="
echo ""
echo "[*] Using branch: $BRANCH"
echo ""
if ! command -v podman &> /dev/null; then
echo "[!] Installing Podman..."
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
elif command -v sudo &> /dev/null; then
SUDO="sudo"
elif command -v doas &> /dev/null; then
SUDO="doas"
else
echo "[X] No sudo/doas found and not running as root. Cannot install podman."
exit 1
fi
if command -v apt &> /dev/null; then
echo "[*] Detected apt (Debian/Ubuntu)"
$SUDO apt update
$SUDO apt install -y podman
elif command -v dnf &> /dev/null; then
echo "[*] Detected dnf (Fedora/RHEL)"
$SUDO dnf install -y podman
elif command -v yum &> /dev/null; then
echo "[*] Detected yum (CentOS/RHEL)"
$SUDO yum install -y podman
elif command -v pacman &> /dev/null; then
echo "[*] Detected pacman (Arch)"
$SUDO pacman -Sy --noconfirm podman
else
echo "[X] Unsupported package manager. Install podman manually."
exit 1
fi
fi
echo "[+] Using Podman"
echo ""
validate_key() {
case "$1" in
*[!0-9a-fA-F]*)
return 1
;;
esac
[ "${#1}" -eq 64 ]
}
echo "Select auth provider:"
echo " 1) jitsi"
echo " 2) telemost"
echo " 3) wbstream"
read -p "Enter choice [1-3, default: 1]: " AUTH_CHOICE
case "$AUTH_CHOICE" in
2)
AUTH="telemost"
;;
3)
AUTH="wbstream"
;;
*)
AUTH="jitsi"
;;
esac
echo "[*] Using auth: $AUTH"
echo ""
echo "Select transport:"
echo " 1) datachannel"
echo " 2) videochannel"
echo " 3) seichannel"
echo " 4) vp8channel"
read -p "Enter choice [1-4, default: 1]: " TRANSPORT_CHOICE
case "$TRANSPORT_CHOICE" in
2)
TRANSPORT="videochannel"
;;
3)
TRANSPORT="seichannel"
;;
4)
TRANSPORT="vp8channel"
;;
*)
TRANSPORT="datachannel"
;;
esac
echo "[*] Using transport: $TRANSPORT"
echo ""
if [ "$AUTH" = "jitsi" ]; then
echo ""
echo "Выберите Jitsi-сервер (проверьте в браузере, какой работает в вашей сети):"
echo " 1) https://meet.small-dm.ru/"
echo " 2) https://meet1.arbitr.ru/"
echo " 3) https://meet.handyweb.org/"
echo " 4) Другой (ввести вручную)"
read -p "Введите номер [1-4, по умолчанию: 1]: " JITSI_SERVER_CHOICE
case "$JITSI_SERVER_CHOICE" in
2)
JITSI_BASE_URL="https://meet1.arbitr.ru"
;;
3)
JITSI_BASE_URL="https://meet.handyweb.org"
;;
4)
read -p "Введите URL Jitsi-сервера: " JITSI_BASE_INPUT
JITSI_BASE_URL="${JITSI_BASE_INPUT%/}"
if [ -z "$JITSI_BASE_URL" ]; then
echo "[X] URL не может быть пустым"
exit 1
fi
;;
*)
JITSI_BASE_URL="https://meet.small-dm.ru"
;;
esac
read -p "Enter Jitsi room name or URL: " JITSI_ROOM_INPUT
if [ -z "$JITSI_ROOM_INPUT" ]; then
echo "[X] Jitsi room name/URL cannot be empty"
exit 1
fi
case "$JITSI_ROOM_INPUT" in
http://*|https://*|*/*)
ROOM_ID="$JITSI_ROOM_INPUT"
;;
*)
ROOM_ID="$JITSI_BASE_URL/$JITSI_ROOM_INPUT"
;;
esac
else
read -p "Enter Room ID: " ROOM_ID
fi
if [ -z "$ROOM_ID" ]; then
echo "[X] Room ID/URL cannot be empty"
exit 1
fi
echo ""
read -p "Enter Encryption Key (hex): " KEY
if [ -z "$KEY" ]; then
echo "[X] Encryption key cannot be empty"
exit 1
fi
if ! validate_key "$KEY"; then
echo "[X] Encryption key must be 64 hex characters"
exit 1
fi
echo ""
read -p "DNS server [default: 8.8.8.8:53]: " DNS_INPUT
DNS=${DNS_INPUT:-8.8.8.8:53}
echo ""
read -p "SOCKS5 ip [default: 127.0.0.1]: " IP_INPUT
SOCKS_IP=${IP_INPUT:-127.0.0.1}
echo ""
read -p "SOCKS5 port [default: 8808]: " PORT_INPUT
SOCKS_PORT=${PORT_INPUT:-8808}
echo ""
read -p "SOCKS5 username (leave empty to disable auth): " SOCKS_USER_INPUT
SOCKS_USER=${SOCKS_USER_INPUT:-}
SOCKS_PASS=""
if [ -n "$SOCKS_USER" ]; then
read -s -p "SOCKS5 password: " SOCKS_PASS_INPUT
echo ""
SOCKS_PASS=${SOCKS_PASS_INPUT:-}
fi
case "$SOCKS_IP" in
127.*|localhost|::1|\[::1\])
;;
*)
if [ -z "$SOCKS_USER" ] || [ -z "$SOCKS_PASS" ]; then
echo "[X] SOCKS auth required when binding outside loopback (set username and password)"
exit 1
fi
;;
esac
# Transport-specific settings
VIDEO_W=1920; VIDEO_H=1080; VIDEO_FPS=30; VIDEO_BITRATE="2M"; VIDEO_HW="none"
VIDEO_CODEC="qrcode"; VIDEO_QR_SIZE=0; VIDEO_QR_RECOVERY="low"
VIDEO_TILE_MODULE=4; VIDEO_TILE_RS=20
VP8_FPS=25; VP8_BATCH=1
SEI_FPS=60; SEI_BATCH=64; SEI_FRAG=900; SEI_ACK=2000
if [ "$TRANSPORT" = "videochannel" ]; then
echo ""
echo "--- Videochannel settings ---"
echo ""
echo "Video codec:"
echo " 1) qrcode"
echo " 2) tile (requires 1080x1080)"
read -p "Enter choice [1-2, default: 1]: " VCODEC_CHOICE
case "$VCODEC_CHOICE" in
2)
VIDEO_CODEC="tile"
VIDEO_W=1080
VIDEO_H=1080
echo "[*] Tile codec selected - forcing 1080x1080"
read -p "Tile module size in pixels 1..270 [default: 4]: " VTILE_MOD_INPUT
VIDEO_TILE_MODULE=${VTILE_MOD_INPUT:-4}
read -p "Tile Reed-Solomon parity percent 0..200 [default: 20]: " VTILE_RS_INPUT
VIDEO_TILE_RS=${VTILE_RS_INPUT:-20}
;;
*)
VIDEO_CODEC="qrcode"
read -p "Video width [default: 1920]: " VW_INPUT
VIDEO_W=${VW_INPUT:-1920}
read -p "Video height [default: 1080]: " VH_INPUT
VIDEO_H=${VH_INPUT:-1080}
read -p "QR error correction (low/medium/high/highest) [default: low]: " VQREC_INPUT
VIDEO_QR_RECOVERY=${VQREC_INPUT:-low}
read -p "QR fragment size bytes [default: 0 (auto)]: " VQRSZ_INPUT
VIDEO_QR_SIZE=${VQRSZ_INPUT:-0}
;;
esac
read -p "Video FPS [default: 30]: " VFPS_INPUT
VIDEO_FPS=${VFPS_INPUT:-30}
read -p "Video bitrate [default: 2M]: " VBRT_INPUT
VIDEO_BITRATE=${VBRT_INPUT:-2M}
read -p "Hardware acceleration (none/nvenc) [default: none]: " VHW_INPUT
VIDEO_HW=${VHW_INPUT:-none}
fi
if [ "$TRANSPORT" = "vp8channel" ]; then
echo ""
echo "--- VP8channel settings ---"
read -p "VP8 FPS [default: 25]: " VP8FPS_INPUT
VP8_FPS=${VP8FPS_INPUT:-25}
read -p "VP8 batch size (frames per tick) [default: 1]: " VP8BATCH_INPUT
VP8_BATCH=${VP8BATCH_INPUT:-1}
fi
if [ "$TRANSPORT" = "seichannel" ]; then
echo ""
echo "--- SEIchannel settings ---"
read -p "SEI FPS [default: 60]: " SEIFPS_INPUT
SEI_FPS=${SEIFPS_INPUT:-60}
read -p "SEI batch size (frames per tick) [default: 64]: " SEIBATCH_INPUT
SEI_BATCH=${SEIBATCH_INPUT:-64}
read -p "SEI fragment size in bytes [default: 900]: " SEIFRAG_INPUT
SEI_FRAG=${SEIFRAG_INPUT:-900}
read -p "SEI ACK timeout in milliseconds [default: 2000]: " SEIACK_INPUT
SEI_ACK=${SEIACK_INPUT:-2000}
fi
echo ""
echo "[*] Cleaning workspace..."
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR"
CACHE_DIR="${OLCRTC_CACHE_DIR:-$HOME/.cache/olcrtc}"
GOMOD_CACHE="$CACHE_DIR/gomod"
GO_BUILD_CACHE="$CACHE_DIR/gobuild"
if [ "$NO_CACHE" = "1" ]; then
echo "[*] --no-cache: purging Go cache at $CACHE_DIR"
chmod -R u+w "$GOMOD_CACHE" "$GO_BUILD_CACHE" 2>/dev/null || true
if ! rm -rf "$GOMOD_CACHE" "$GO_BUILD_CACHE" 2>/dev/null; then
echo "[*] Falling back to in-container purge (files owned by container UID)..."
podman run --rm \
-v "$CACHE_DIR":/cache:Z \
"$IMAGE_NAME" \
sh -c 'rm -rf /cache/gomod /cache/gobuild'
fi
fi
mkdir -p "$GOMOD_CACHE" "$GO_BUILD_CACHE"
echo "[*] Using Go cache: $CACHE_DIR"
echo "[*] Cloning repository..."
git clone --depth 1 --recurse-submodules --branch "$BRANCH" "$REPO_URL" "$WORK_DIR"
echo "[*] Pulling Go image..."
podman pull "$IMAGE_NAME"
echo "[*] Building OlcRTC..."
podman run --rm \
--network host \
-v "$WORK_DIR":/app:Z \
-v "$GOMOD_CACHE":/go/pkg/mod:Z \
-v "$GO_BUILD_CACHE":/root/.cache/go-build:Z \
-w /app \
"$IMAGE_NAME" \
sh -c "go mod download && go build -trimpath -ldflags='-s -w' -o olcrtc ./cmd/olcrtc"
if [ ! -f "$WORK_DIR/olcrtc" ]; then
echo "[X] Build failed"
exit 1
fi
# Generate YAML config
CONFIG_FILE="$WORK_DIR/client.yaml"
cat > "$CONFIG_FILE" <<EOF
mode: cnc
auth:
provider: "$AUTH"
room:
id: "$ROOM_ID"
crypto:
key: "$KEY"
net:
transport: "$TRANSPORT"
dns: "$DNS"
socks:
host: "$SOCKS_IP"
port: $SOCKS_PORT
EOF
if [ -n "$SOCKS_USER" ]; then
cat >> "$CONFIG_FILE" <<EOF
user: "$SOCKS_USER"
pass: "$SOCKS_PASS"
EOF
fi
if [ "$TRANSPORT" = "vp8channel" ]; then
cat >> "$CONFIG_FILE" <<EOF
vp8:
fps: $VP8_FPS
batch_size: $VP8_BATCH
EOF
fi
if [ "$TRANSPORT" = "seichannel" ]; then
cat >> "$CONFIG_FILE" <<EOF
sei:
fps: $SEI_FPS
batch_size: $SEI_BATCH
fragment_size: $SEI_FRAG
ack_timeout_ms: $SEI_ACK
EOF
fi
if [ "$TRANSPORT" = "videochannel" ]; then
cat >> "$CONFIG_FILE" <<EOF
video:
width: $VIDEO_W
height: $VIDEO_H
fps: $VIDEO_FPS
bitrate: "$VIDEO_BITRATE"
hw: $VIDEO_HW
codec: $VIDEO_CODEC
qr_size: $VIDEO_QR_SIZE
qr_recovery: $VIDEO_QR_RECOVERY
tile_module: $VIDEO_TILE_MODULE
tile_rs: $VIDEO_TILE_RS
EOF
fi
cat >> "$CONFIG_FILE" <<EOF
data: data
debug: false
EOF
echo "[*] Starting OlcRTC client..."
START_CMD="./olcrtc client.yaml"
if [ "$TRANSPORT" = "videochannel" ]; then
START_CMD="apk add --no-cache ffmpeg >/dev/null && ./olcrtc client.yaml"
fi
podman run -d \
--name "$CONTAINER_NAME" \
--network host \
--restart unless-stopped \
-v "$WORK_DIR":/app:Z \
-w /app \
"$IMAGE_NAME" \
sh -c "$START_CMD"
sleep 2
echo ""
echo "[+] Client started successfully!"
echo ""
echo "Container name: $CONTAINER_NAME"
echo "Auth: $AUTH"
echo "Transport: $TRANSPORT"
echo "Room ID/URL: $ROOM_ID"
if [ -n "$SOCKS_USER" ]; then
echo "SOCKS5 proxy: $SOCKS_IP:$SOCKS_PORT (auth: $SOCKS_USER)"
else
echo "SOCKS5 proxy: $SOCKS_IP:$SOCKS_PORT"
fi
echo ""
echo "View logs:"
echo " podman logs -f $CONTAINER_NAME"
echo ""
echo "Stop client:"
echo " podman stop $CONTAINER_NAME"
echo ""
echo "Test proxy:"
if [ -n "$SOCKS_USER" ]; then
echo " curl --socks5-hostname $SOCKS_USER:$SOCKS_PASS@$SOCKS_IP:$SOCKS_PORT https://icanhazip.com"
else
echo " curl --socks5-hostname $SOCKS_IP:$SOCKS_PORT https://icanhazip.com"
fi
echo ""

View File

@@ -1,175 +0,0 @@
#!/bin/sh
set -eu
die() {
echo "olcrtc-entrypoint: $*" >&2
exit 1
}
if [ "${1:-}" = "olcrtc" ]; then
shift
fi
if [ "$#" -gt 0 ]; then
exec /usr/local/bin/olcrtc "$@"
fi
mode="${OLCRTC_MODE:-srv}"
room_id="${OLCRTC_ROOM_ID:-}"
carrier="${OLCRTC_CARRIER:-${OLCRTC_AUTH:-}}"
transport="${OLCRTC_TRANSPORT:-}"
data_dir="${OLCRTC_DATA_DIR:-/usr/share/olcrtc}"
dns_server="${OLCRTC_DNS:-8.8.8.8:53}"
key="${OLCRTC_KEY:-}"
key_file="${OLCRTC_KEY_FILE:-/var/lib/olcrtc/key.hex}"
socks_proxy="${OLCRTC_SOCKS_PROXY:-}"
socks_proxy_port="${OLCRTC_SOCKS_PROXY_PORT:-1080}"
socks_host="${OLCRTC_SOCKS_HOST:-127.0.0.1}"
socks_port="${OLCRTC_SOCKS_PORT:-8808}"
socks_user="${OLCRTC_SOCKS_USER:-}"
socks_pass="${OLCRTC_SOCKS_PASS:-}"
video_w="${OLCRTC_VIDEO_W:-0}"
video_h="${OLCRTC_VIDEO_H:-0}"
video_fps="${OLCRTC_VIDEO_FPS:-0}"
video_bitrate="${OLCRTC_VIDEO_BITRATE:-}"
video_hw="${OLCRTC_VIDEO_HW:-none}"
video_codec="${OLCRTC_VIDEO_CODEC:-qrcode}"
video_qr_size="${OLCRTC_VIDEO_QR_SIZE:-0}"
video_qr_recovery="${OLCRTC_VIDEO_QR_RECOVERY:-low}"
video_tile_module="${OLCRTC_VIDEO_TILE_MODULE:-0}"
video_tile_rs="${OLCRTC_VIDEO_TILE_RS:-0}"
vp8_fps="${OLCRTC_VP8_FPS:-0}"
vp8_batch="${OLCRTC_VP8_BATCH:-0}"
sei_fps="${OLCRTC_SEI_FPS:-0}"
sei_batch="${OLCRTC_SEI_BATCH:-0}"
sei_frag="${OLCRTC_SEI_FRAG:-0}"
sei_ack="${OLCRTC_SEI_ACK:-0}"
debug="${OLCRTC_DEBUG:-false}"
ffmpeg="${OLCRTC_FFMPEG:-ffmpeg}"
case "$mode" in
srv|cnc) ;;
*) die "set OLCRTC_MODE to srv or cnc" ;;
esac
[ -n "$carrier" ] || die "set OLCRTC_CARRIER (e.g. jitsi, telemost, wbstream)"
[ -n "$transport" ] || die "set OLCRTC_TRANSPORT (e.g. datachannel, videochannel, seichannel, vp8channel)"
make_key() {
if command -v od >/dev/null 2>&1; then
od -An -N32 -tx1 /dev/urandom | tr -d ' \n'
else
hexdump -n 32 -e '32/1 "%02x"' /dev/urandom
fi
}
if [ -z "$room_id" ]; then
die "set OLCRTC_ROOM_ID to the room identifier"
fi
if [ -z "$key" ]; then
if [ -s "$key_file" ]; then
key="$(tr -d '[:space:]' < "$key_file")"
elif [ "$mode" = "srv" ]; then
key="$(make_key)"
umask 077
printf '%s\n' "$key" > "$key_file"
echo "olcrtc-entrypoint: generated encryption key and saved it to $key_file" >&2
echo "olcrtc-entrypoint: OLCRTC_KEY=$key" >&2
else
die "set OLCRTC_KEY or mount OLCRTC_KEY_FILE with the server encryption key"
fi
fi
case "$key" in
*[!0-9a-fA-F]*)
die "OLCRTC_KEY must be a 64-character hex string"
;;
esac
[ "${#key}" -eq 64 ] || die "OLCRTC_KEY must be 64 hex characters"
# Generate YAML config
config="/tmp/olcrtc-${mode}.yaml"
cat > "$config" <<EOF
mode: $mode
auth:
provider: "$carrier"
room:
id: "$room_id"
crypto:
key: "$key"
net:
transport: "$transport"
dns: "$dns_server"
data: "$data_dir"
EOF
if [ "$mode" = "srv" ] && [ -n "$socks_proxy" ]; then
cat >> "$config" <<EOF
socks:
proxy_addr: "$socks_proxy"
proxy_port: $socks_proxy_port
EOF
fi
if [ "$mode" = "cnc" ]; then
cat >> "$config" <<EOF
socks:
host: "$socks_host"
port: $socks_port
EOF
if [ -n "$socks_user" ]; then
cat >> "$config" <<EOF
user: "$socks_user"
pass: "$socks_pass"
EOF
fi
fi
if [ "$transport" = "videochannel" ]; then
cat >> "$config" <<EOF
video:
width: $video_w
height: $video_h
fps: $video_fps
hw: $video_hw
codec: $video_codec
qr_recovery: $video_qr_recovery
EOF
[ -n "$video_bitrate" ] && printf ' bitrate: "%s"\n' "$video_bitrate" >> "$config"
[ "$video_qr_size" -gt 0 ] 2>/dev/null && printf ' qr_size: %s\n' "$video_qr_size" >> "$config"
[ "$video_tile_module" -gt 0 ] 2>/dev/null && printf ' tile_module: %s\n' "$video_tile_module" >> "$config"
[ "$video_tile_rs" -gt 0 ] 2>/dev/null && printf ' tile_rs: %s\n' "$video_tile_rs" >> "$config"
fi
if [ "$transport" = "vp8channel" ]; then
cat >> "$config" <<EOF
vp8:
fps: $vp8_fps
batch_size: $vp8_batch
EOF
fi
if [ "$transport" = "seichannel" ]; then
cat >> "$config" <<EOF
sei:
fps: $sei_fps
batch_size: $sei_batch
fragment_size: $sei_frag
ack_timeout_ms: $sei_ack
EOF
fi
case "${debug}" in
1|true|TRUE|yes|YES|on|ON)
printf 'debug: true\n' >> "$config"
;;
esac
[ -n "$ffmpeg" ] && printf 'ffmpeg: "%s"\n' "$ffmpeg" >> "$config"
exec /usr/local/bin/olcrtc "$config"

View File

@@ -1,4 +0,0 @@
#!/bin/sh
set -eu
pidof olcrtc >/dev/null 2>&1

View File

@@ -1,532 +0,0 @@
#!/bin/bash
echo "ЕСЛИ У ВАС ЕСТЬ ПРОБЛЕМЫ - Я В КУРСЕ, ПРОЕКТ В БЕТЕ, ПО ПРОБЛЕМАМ В ЧАТ t.me/openlibrecommunity ИЛИ ВООБЩЕ НЕКУДА, ЖДИТЕ РЕЛИЗА"
set -e
PODMAN_ID=$(tr -dc 'a-z0-9' </dev/urandom | head -c 8)
CONTAINER_NAME="olcrtc-server-$PODMAN_ID"
IMAGE_NAME="docker.io/library/golang:1.26-alpine3.22"
REPO_URL="https://github.com/openlibrecommunity/olcrtc.git"
WORK_DIR="/tmp/olcrtc-deploy-$PODMAN_ID"
BRANCH="master"
NO_CACHE=0
while [[ $# -gt 0 ]]; do
case $1 in
--branch=*)
BRANCH="${1#*=}"
shift
;;
--no-cache)
NO_CACHE=1
shift
;;
*)
shift
;;
esac
done
echo "=== OlcRTC Server Deployment Script ==="
echo ""
echo "[*] Using branch: $BRANCH"
echo ""
if ! command -v podman &> /dev/null; then
echo "[!] Installing Podman..."
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
elif command -v sudo &> /dev/null; then
SUDO="sudo"
elif command -v doas &> /dev/null; then
SUDO="doas"
else
echo "[X] No sudo/doas found and not running as root. Cannot install podman."
exit 1
fi
if command -v apt &> /dev/null; then
echo "[*] Detected apt (Debian/Ubuntu)"
$SUDO apt update
$SUDO apt install -y podman
elif command -v dnf &> /dev/null; then
echo "[*] Detected dnf (Fedora/RHEL)"
$SUDO dnf install -y podman
elif command -v yum &> /dev/null; then
echo "[*] Detected yum (CentOS/RHEL)"
$SUDO yum install -y podman
elif command -v pacman &> /dev/null; then
echo "[*] Detected pacman (Arch)"
$SUDO pacman -Sy --noconfirm podman
else
echo "[X] Unsupported package manager. Install podman manually."
exit 1
fi
fi
echo "[+] Using Podman"
echo ""
validate_key() {
case "$1" in
*[!0-9a-fA-F]*)
return 1
;;
esac
[ "${#1}" -eq 64 ]
}
echo "Select carrier:"
echo " 1) jitsi"
echo " 2) telemost"
echo " 3) wbstream"
read -p "Enter choice [1-3, default: 1]: " CARRIER_CHOICE
case "$CARRIER_CHOICE" in
2)
CARRIER="telemost"
;;
3)
CARRIER="wbstream"
;;
*)
CARRIER="jitsi"
;;
esac
echo "[*] Using carrier: $CARRIER"
echo ""
echo "Select transport:"
echo " 1) datachannel"
echo " 2) videochannel"
echo " 3) seichannel"
echo " 4) vp8channel"
read -p "Enter choice [1-4, default: 1]: " TRANSPORT_CHOICE
case "$TRANSPORT_CHOICE" in
2)
TRANSPORT="videochannel"
;;
3)
TRANSPORT="seichannel"
;;
4)
TRANSPORT="vp8channel"
;;
*)
TRANSPORT="datachannel"
;;
esac
echo "[*] Using transport: $TRANSPORT"
echo ""
GEN_ROOM=0
if [ "$CARRIER" = "jitsi" ]; then
echo ""
echo "Выберите Jitsi-сервер (проверьте в браузере, какой работает в вашей сети):"
echo " 1) https://meet.small-dm.ru/"
echo " 2) https://meet1.arbitr.ru/"
echo " 3) https://meet.handyweb.org/"
echo " 4) Другой (ввести вручную)"
read -p "Введите номер [1-4, по умолчанию: 1]: " JITSI_SERVER_CHOICE
case "$JITSI_SERVER_CHOICE" in
2)
JITSI_BASE_URL="https://meet1.arbitr.ru"
;;
3)
JITSI_BASE_URL="https://meet.handyweb.org"
;;
4)
read -p "Введите URL Jitsi-сервера: " JITSI_BASE_INPUT
JITSI_BASE_URL="${JITSI_BASE_INPUT%/}"
if [ -z "$JITSI_BASE_URL" ]; then
echo "[X] URL не может быть пустым"
exit 1
fi
;;
*)
JITSI_BASE_URL="https://meet.small-dm.ru"
;;
esac
echo "Room options:"
echo " 1) Auto-generate new room (recommended)"
echo " 2) Use specific room name or URL"
read -p "Enter choice [1-2, default: 1]: " ROOM_CHOICE
case "$ROOM_CHOICE" in
2)
read -p "Enter Jitsi room name or URL: " JITSI_ROOM_INPUT
if [ -z "$JITSI_ROOM_INPUT" ]; then
echo "[X] Jitsi room name/URL cannot be empty"
exit 1
fi
case "$JITSI_ROOM_INPUT" in
http://*|https://*|*/*)
ROOM_ID="$JITSI_ROOM_INPUT"
;;
*)
ROOM_ID="$JITSI_BASE_URL/$JITSI_ROOM_INPUT"
;;
esac
;;
*)
JITSI_ROOM="olcrtc-$PODMAN_ID"
ROOM_ID="$JITSI_BASE_URL/$JITSI_ROOM"
echo "[*] Generated Jitsi room URL: $ROOM_ID"
;;
esac
else
read -p "Enter Room ID: " ROOM_ID
if [ -z "$ROOM_ID" ]; then
echo "[X] Room ID/URL cannot be empty"
exit 1
fi
fi
echo ""
read -p "DNS server [default: 8.8.8.8:53]: " DNS_INPUT
DNS=${DNS_INPUT:-8.8.8.8:53}
echo ""
read -p "Use SOCKS5 proxy for egress? (y/N): " USE_PROXY
SOCKS_PROXY_ADDR=""
SOCKS_PROXY_PORT=0
if [[ "$USE_PROXY" =~ ^[Yy]$ ]]; then
read -p "Enter SOCKS5 proxy address [default: 127.0.0.1]: " PROXY_ADDR_INPUT
SOCKS_PROXY_ADDR=${PROXY_ADDR_INPUT:-127.0.0.1}
read -p "Enter SOCKS5 proxy port [default: 1080]: " PROXY_PORT_INPUT
SOCKS_PROXY_PORT=${PROXY_PORT_INPUT:-1080}
echo "[*] Will use SOCKS5 proxy: $SOCKS_PROXY_ADDR:$SOCKS_PROXY_PORT"
fi
# Transport-specific settings
VIDEO_W=1920; VIDEO_H=1080; VIDEO_FPS=30; VIDEO_BITRATE="2M"; VIDEO_HW="none"
VIDEO_CODEC="qrcode"; VIDEO_QR_SIZE=0; VIDEO_QR_RECOVERY="low"
VIDEO_TILE_MODULE=4; VIDEO_TILE_RS=20
VP8_FPS=25; VP8_BATCH=1
SEI_FPS=60; SEI_BATCH=64; SEI_FRAG=900; SEI_ACK=2000
if [ "$TRANSPORT" = "videochannel" ]; then
echo ""
echo "--- Videochannel settings ---"
echo ""
echo "Video codec:"
echo " 1) qrcode"
echo " 2) tile (requires 1080x1080)"
read -p "Enter choice [1-2, default: 1]: " VCODEC_CHOICE
case "$VCODEC_CHOICE" in
2)
VIDEO_CODEC="tile"
VIDEO_W=1080
VIDEO_H=1080
echo "[*] Tile codec selected - forcing 1080x1080"
read -p "Tile module size in pixels 1..270 [default: 4]: " VTILE_MOD_INPUT
VIDEO_TILE_MODULE=${VTILE_MOD_INPUT:-4}
read -p "Tile Reed-Solomon parity percent 0..200 [default: 20]: " VTILE_RS_INPUT
VIDEO_TILE_RS=${VTILE_RS_INPUT:-20}
;;
*)
VIDEO_CODEC="qrcode"
read -p "Video width [default: 1920]: " VW_INPUT
VIDEO_W=${VW_INPUT:-1920}
read -p "Video height [default: 1080]: " VH_INPUT
VIDEO_H=${VH_INPUT:-1080}
read -p "QR error correction (low/medium/high/highest) [default: low]: " VQREC_INPUT
VIDEO_QR_RECOVERY=${VQREC_INPUT:-low}
read -p "QR fragment size bytes [default: 0 (auto)]: " VQRSZ_INPUT
VIDEO_QR_SIZE=${VQRSZ_INPUT:-0}
;;
esac
read -p "Video FPS [default: 30]: " VFPS_INPUT
VIDEO_FPS=${VFPS_INPUT:-30}
read -p "Video bitrate [default: 2M]: " VBRT_INPUT
VIDEO_BITRATE=${VBRT_INPUT:-2M}
read -p "Hardware acceleration (none/nvenc) [default: none]: " VHW_INPUT
VIDEO_HW=${VHW_INPUT:-none}
fi
if [ "$TRANSPORT" = "vp8channel" ]; then
echo ""
echo "--- VP8channel settings ---"
read -p "VP8 FPS [default: 25]: " VP8FPS_INPUT
VP8_FPS=${VP8FPS_INPUT:-25}
read -p "VP8 batch size (frames per tick) [default: 1]: " VP8BATCH_INPUT
VP8_BATCH=${VP8BATCH_INPUT:-1}
fi
if [ "$TRANSPORT" = "seichannel" ]; then
echo ""
echo "--- SEIchannel settings ---"
read -p "SEI FPS [default: 60]: " SEIFPS_INPUT
SEI_FPS=${SEIFPS_INPUT:-60}
read -p "SEI batch size (frames per tick) [default: 64]: " SEIBATCH_INPUT
SEI_BATCH=${SEIBATCH_INPUT:-64}
read -p "SEI fragment size in bytes [default: 900]: " SEIFRAG_INPUT
SEI_FRAG=${SEIFRAG_INPUT:-900}
read -p "SEI ACK timeout in milliseconds [default: 2000]: " SEIACK_INPUT
SEI_ACK=${SEIACK_INPUT:-2000}
fi
echo ""
echo "[*] Cleaning workspace..."
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR"
CACHE_DIR="${OLCRTC_CACHE_DIR:-$HOME/.cache/olcrtc}"
GOMOD_CACHE="$CACHE_DIR/gomod"
GO_BUILD_CACHE="$CACHE_DIR/gobuild"
if [ "$NO_CACHE" = "1" ]; then
echo "[*] --no-cache: purging Go cache at $CACHE_DIR"
chmod -R u+w "$GOMOD_CACHE" "$GO_BUILD_CACHE" 2>/dev/null || true
if ! rm -rf "$GOMOD_CACHE" "$GO_BUILD_CACHE" 2>/dev/null; then
echo "[*] Falling back to in-container purge (files owned by container UID)..."
podman run --rm \
-v "$CACHE_DIR":/cache:Z \
"$IMAGE_NAME" \
sh -c 'rm -rf /cache/gomod /cache/gobuild'
fi
fi
mkdir -p "$GOMOD_CACHE" "$GO_BUILD_CACHE"
echo "[*] Using Go cache: $CACHE_DIR"
echo "[*] Cloning repository..."
git clone --depth 1 --recurse-submodules --branch "$BRANCH" "$REPO_URL" "$WORK_DIR"
echo "[*] Pulling Go image..."
podman pull "$IMAGE_NAME"
echo "[*] Building OlcRTC..."
podman run --rm \
--network host \
-v "$WORK_DIR":/app:Z \
-v "$GOMOD_CACHE":/go/pkg/mod:Z \
-v "$GO_BUILD_CACHE":/root/.cache/go-build:Z \
-w /app \
"$IMAGE_NAME" \
sh -c "go mod download && go build -trimpath -ldflags='-s -w' -o olcrtc ./cmd/olcrtc"
if [ ! -f "$WORK_DIR/olcrtc" ]; then
echo "[X] Build failed"
exit 1
fi
if [ "$GEN_ROOM" = "1" ]; then
echo "[*] Generating room via mode: gen..."
GEN_CONFIG="$WORK_DIR/gen.yaml"
cat > "$GEN_CONFIG" <<GENEOF
mode: gen
auth:
provider: "$CARRIER"
net:
dns: "$DNS"
gen:
amount: 1
data: data
GENEOF
ROOM_ID=$(podman run --rm \
--network host \
-v "$WORK_DIR":/app:Z \
-w /app \
"$IMAGE_NAME" \
./olcrtc gen.yaml)
if [ -z "$ROOM_ID" ]; then
echo "[X] Room generation failed"
exit 1
fi
echo "[+] Generated room ID: $ROOM_ID"
fi
KEY_FILE="$HOME/.olcrtc_key"
if [ -f "$KEY_FILE" ]; then
echo "[*] Loading existing encryption key..."
KEY=$(tr -d '[:space:]' < "$KEY_FILE")
if ! validate_key "$KEY"; then
echo "[X] Invalid encryption key in $KEY_FILE"
echo " Remove the file to generate a new key, or replace it with 64 hex characters."
exit 1
fi
else
echo "[*] Generating new encryption key..."
KEY=$(openssl rand -hex 32)
echo "$KEY" > "$KEY_FILE"
chmod 600 "$KEY_FILE"
echo ""
echo "=========================================="
echo "NEW ENCRYPTION KEY (saved to $KEY_FILE):"
echo "$KEY"
echo "=========================================="
echo ""
fi
# Generate YAML config
CONFIG_FILE="$WORK_DIR/server.yaml"
cat > "$CONFIG_FILE" <<EOF
mode: srv
auth:
provider: "$CARRIER"
room:
id: "$ROOM_ID"
crypto:
key: "$KEY"
net:
transport: "$TRANSPORT"
dns: "$DNS"
EOF
if [ -n "$SOCKS_PROXY_ADDR" ]; then
cat >> "$CONFIG_FILE" <<EOF
socks:
proxy_addr: "$SOCKS_PROXY_ADDR"
proxy_port: $SOCKS_PROXY_PORT
EOF
fi
if [ "$TRANSPORT" = "vp8channel" ]; then
cat >> "$CONFIG_FILE" <<EOF
vp8:
fps: $VP8_FPS
batch_size: $VP8_BATCH
EOF
fi
if [ "$TRANSPORT" = "seichannel" ]; then
cat >> "$CONFIG_FILE" <<EOF
sei:
fps: $SEI_FPS
batch_size: $SEI_BATCH
fragment_size: $SEI_FRAG
ack_timeout_ms: $SEI_ACK
EOF
fi
if [ "$TRANSPORT" = "videochannel" ]; then
cat >> "$CONFIG_FILE" <<EOF
video:
width: $VIDEO_W
height: $VIDEO_H
fps: $VIDEO_FPS
bitrate: "$VIDEO_BITRATE"
hw: $VIDEO_HW
codec: $VIDEO_CODEC
qr_size: $VIDEO_QR_SIZE
qr_recovery: $VIDEO_QR_RECOVERY
tile_module: $VIDEO_TILE_MODULE
tile_rs: $VIDEO_TILE_RS
EOF
fi
cat >> "$CONFIG_FILE" <<EOF
data: data
debug: false
EOF
echo "[*] Starting OlcRTC server..."
START_CMD="./olcrtc server.yaml"
if [ "$TRANSPORT" = "videochannel" ]; then
START_CMD="apk add --no-cache ffmpeg >/dev/null && ./olcrtc server.yaml"
fi
podman run -d \
--network host \
--name "$CONTAINER_NAME" \
--restart unless-stopped \
-v "$WORK_DIR":/app:Z \
-w /app \
"$IMAGE_NAME" \
sh -c "$START_CMD"
read -p "Enter a comment for the config (default: olc - t.me/openlibrecommunity): " sub_configname
if [ -z "$sub_configname" ]; then
sub_configname="olc - t.me/openlibrecommunity"
fi
echo ""
echo "[+] Server started successfully!"
echo ""
echo "Container name: $CONTAINER_NAME"
echo "Carrier: $CARRIER"
echo "Transport: $TRANSPORT"
echo "Room ID/URL: $ROOM_ID"
echo "Encryption key: $KEY"
echo ""
TRANSPORT_PAYLOAD=""
if [ "$TRANSPORT" = "vp8channel" ]; then
TRANSPORT_PAYLOAD="<vp8-fps=${VP8_FPS}&vp8-batch=${VP8_BATCH}>"
elif [ "$TRANSPORT" = "seichannel" ]; then
TRANSPORT_PAYLOAD="<fps=${SEI_FPS}&batch=${SEI_BATCH}&frag=${SEI_FRAG}&ack-ms=${SEI_ACK}>"
elif [ "$TRANSPORT" = "videochannel" ]; then
TRANSPORT_PAYLOAD="<video-w=${VIDEO_W}&video-h=${VIDEO_H}&video-fps=${VIDEO_FPS}&video-bitrate=${VIDEO_BITRATE}&video-hw=${VIDEO_HW}&video-codec=${VIDEO_CODEC}>"
if [ "$VIDEO_CODEC" = "tile" ]; then
TRANSPORT_PAYLOAD="<video-w=${VIDEO_W}&video-h=${VIDEO_H}&video-fps=${VIDEO_FPS}&video-bitrate=${VIDEO_BITRATE}&video-hw=${VIDEO_HW}&video-codec=${VIDEO_CODEC}&video-tile-module=${VIDEO_TILE_MODULE}&video-tile-rs=${VIDEO_TILE_RS}>"
elif [ "$VIDEO_QR_SIZE" -gt 0 ] 2>/dev/null; then
TRANSPORT_PAYLOAD="<video-w=${VIDEO_W}&video-h=${VIDEO_H}&video-fps=${VIDEO_FPS}&video-bitrate=${VIDEO_BITRATE}&video-hw=${VIDEO_HW}&video-codec=${VIDEO_CODEC}&video-qr-recovery=${VIDEO_QR_RECOVERY}&video-qr-size=${VIDEO_QR_SIZE}>"
else
TRANSPORT_PAYLOAD="<video-w=${VIDEO_W}&video-h=${VIDEO_H}&video-fps=${VIDEO_FPS}&video-bitrate=${VIDEO_BITRATE}&video-hw=${VIDEO_HW}&video-codec=${VIDEO_CODEC}&video-qr-recovery=${VIDEO_QR_RECOVERY}>"
fi
fi
OLC_URI="olcrtc://$CARRIER?${TRANSPORT}${TRANSPORT_PAYLOAD}@$ROOM_ID#$KEY\$$sub_configname"
echo "uri: $OLC_URI"
echo ""
GR_BIN="$WORK_DIR/gr"
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;;
esac
GR_URL="https://github.com/zarazaex69/gr/releases/latest/download/gr-${OS}-${ARCH}"
if curl -fsSL "$GR_URL" -o "$GR_BIN" 2>/dev/null; then
chmod +x "$GR_BIN"
echo "[*] QR code for your URI (scan with olcbox):"
echo ""
"$GR_BIN" -o -s "$OLC_URI" 2>/dev/null || echo "[!] QR generation failed"
echo ""
else
echo "[!] Could not download gr ($GR_URL), skipping QR"
fi
if [ -n "$SOCKS_PROXY_ADDR" ]; then
echo "SOCKS5 proxy: $SOCKS_PROXY_ADDR:$SOCKS_PROXY_PORT"
fi
echo ""
echo "View logs:"
echo " podman logs -f $CONTAINER_NAME"
echo ""
echo "Stop server:"
echo " podman stop $CONTAINER_NAME"
echo ""