diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 50cd5b4..0000000 --- a/.dockerignore +++ /dev/null @@ -1,19 +0,0 @@ -.git -.gitignore - -Dockerfile -docker-compose*.yml - -code/ -doc/ -asset/ - -*.log -*.tmp -tmp/ - -olcrtc -build/ -dist/ -coverage.out -GEMINI.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f8b7bf0..cc540cf 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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 `. - Если запуск через podman - `podman logs -f `. 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 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7f49d11..953363a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -38,7 +38,7 @@ - [ ] session / app (`internal/app/session`, `pkg/`) - [ ] config / CLI / URI / sub - [ ] mobile (`mobile/`) -- [ ] CI / Docker / mage +- [ ] CI / mage - [ ] docs / examples - [ ] другое: diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 58a6998..0000000 --- a/Dockerfile +++ /dev/null @@ -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"] diff --git a/cmd/olcrtc/main_test.go b/cmd/olcrtc/main_test.go index 916bae3..0331167 100644 --- a/cmd/olcrtc/main_test.go +++ b/cmd/olcrtc/main_test.go @@ -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 } diff --git a/docker-compose.client.yml b/docker-compose.client.yml deleted file mode 100644 index 6221718..0000000 --- a/docker-compose.client.yml +++ /dev/null @@ -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: diff --git a/docker-compose.server.yml b/docker-compose.server.yml deleted file mode 100644 index a83525e..0000000 --- a/docker-compose.server.yml +++ /dev/null @@ -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: diff --git a/docs/about.md b/docs/about.md index b5ffbf9..6d60a4c 100644 --- a/docs/about.md +++ b/docs/about.md @@ -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 diff --git a/docs/docker.md b/docs/docker.md deleted file mode 100644 index 54a7d7d..0000000 --- a/docs/docker.md +++ /dev/null @@ -1,148 +0,0 @@ -
- - - -![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) - -
- - -# Локальная настройка 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) diff --git a/docs/examples/client/client.jitsi.seichannel.yaml b/docs/examples/client/client.jitsi.seichannel.yaml index 5083014..22cf484 100644 --- a/docs/examples/client/client.jitsi.seichannel.yaml +++ b/docs/examples/client/client.jitsi.seichannel.yaml @@ -33,7 +33,7 @@ socks: pass: "" sei: - fps: 60 + fps: 30 batch_size: 64 fragment_size: 900 ack_timeout_ms: 2000 diff --git a/docs/examples/client/client.jitsi.vp8channel.yaml b/docs/examples/client/client.jitsi.vp8channel.yaml index d2c459b..b2ae7b7 100644 --- a/docs/examples/client/client.jitsi.vp8channel.yaml +++ b/docs/examples/client/client.jitsi.vp8channel.yaml @@ -33,7 +33,7 @@ socks: pass: "" vp8: - fps: 60 + fps: 30 batch_size: 64 data: data diff --git a/docs/examples/client/client.telemost.seichannel.yaml b/docs/examples/client/client.telemost.seichannel.yaml index a4b9f0e..57819b2 100644 --- a/docs/examples/client/client.telemost.seichannel.yaml +++ b/docs/examples/client/client.telemost.seichannel.yaml @@ -31,7 +31,7 @@ socks: pass: "" sei: - fps: 60 + fps: 30 batch_size: 64 fragment_size: 900 ack_timeout_ms: 2000 diff --git a/docs/examples/client/client.telemost.vp8channel.yaml b/docs/examples/client/client.telemost.vp8channel.yaml index b0dc7ce..83e4984 100644 --- a/docs/examples/client/client.telemost.vp8channel.yaml +++ b/docs/examples/client/client.telemost.vp8channel.yaml @@ -31,7 +31,7 @@ socks: pass: "" vp8: - fps: 60 + fps: 30 batch_size: 64 data: data diff --git a/docs/examples/client/client.wbstream.seichannel.yaml b/docs/examples/client/client.wbstream.seichannel.yaml index 391583c..fb9bc32 100644 --- a/docs/examples/client/client.wbstream.seichannel.yaml +++ b/docs/examples/client/client.wbstream.seichannel.yaml @@ -31,7 +31,7 @@ socks: pass: "" sei: - fps: 60 + fps: 30 batch_size: 64 fragment_size: 900 ack_timeout_ms: 2000 diff --git a/docs/examples/client/client.wbstream.vp8channel.yaml b/docs/examples/client/client.wbstream.vp8channel.yaml index f40dbfa..db69f76 100644 --- a/docs/examples/client/client.wbstream.vp8channel.yaml +++ b/docs/examples/client/client.wbstream.vp8channel.yaml @@ -31,7 +31,7 @@ socks: pass: "" vp8: - fps: 60 + fps: 30 batch_size: 64 data: data diff --git a/docs/examples/server/.env.telemost.server.example b/docs/examples/server/.env.telemost.server.example deleted file mode 100644 index fc3e57e..0000000 --- a/docs/examples/server/.env.telemost.server.example +++ /dev/null @@ -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 diff --git a/docs/examples/server/server.jitsi.seichannel.yaml b/docs/examples/server/server.jitsi.seichannel.yaml index 7985e4b..3d4e89f 100644 --- a/docs/examples/server/server.jitsi.seichannel.yaml +++ b/docs/examples/server/server.jitsi.seichannel.yaml @@ -34,7 +34,7 @@ socks: proxy_pass: "" sei: - fps: 60 + fps: 30 batch_size: 64 fragment_size: 900 ack_timeout_ms: 2000 diff --git a/docs/examples/server/server.jitsi.vp8channel.yaml b/docs/examples/server/server.jitsi.vp8channel.yaml index ba02bb5..12b071c 100644 --- a/docs/examples/server/server.jitsi.vp8channel.yaml +++ b/docs/examples/server/server.jitsi.vp8channel.yaml @@ -34,7 +34,7 @@ socks: proxy_pass: "" vp8: - fps: 60 + fps: 30 batch_size: 64 data: data diff --git a/docs/examples/server/server.telemost.seichannel.yaml b/docs/examples/server/server.telemost.seichannel.yaml index 42aebed..31d4260 100644 --- a/docs/examples/server/server.telemost.seichannel.yaml +++ b/docs/examples/server/server.telemost.seichannel.yaml @@ -32,7 +32,7 @@ socks: proxy_pass: "" sei: - fps: 60 + fps: 30 batch_size: 64 fragment_size: 900 ack_timeout_ms: 2000 diff --git a/docs/examples/server/server.telemost.vp8channel.yaml b/docs/examples/server/server.telemost.vp8channel.yaml index bae7488..b74c163 100644 --- a/docs/examples/server/server.telemost.vp8channel.yaml +++ b/docs/examples/server/server.telemost.vp8channel.yaml @@ -32,7 +32,7 @@ socks: proxy_pass: "" vp8: - fps: 60 + fps: 30 batch_size: 64 data: data diff --git a/docs/examples/server/server.wbstream.seichannel.yaml b/docs/examples/server/server.wbstream.seichannel.yaml index 18366ec..05fcc95 100644 --- a/docs/examples/server/server.wbstream.seichannel.yaml +++ b/docs/examples/server/server.wbstream.seichannel.yaml @@ -32,7 +32,7 @@ socks: proxy_pass: "" sei: - fps: 60 + fps: 30 batch_size: 64 fragment_size: 900 ack_timeout_ms: 2000 diff --git a/docs/examples/server/server.wbstream.vp8channel.yaml b/docs/examples/server/server.wbstream.vp8channel.yaml index d7d1cad..f623487 100644 --- a/docs/examples/server/server.wbstream.vp8channel.yaml +++ b/docs/examples/server/server.wbstream.vp8channel.yaml @@ -32,7 +32,7 @@ socks: proxy_pass: "" vp8: - fps: 60 + fps: 30 batch_size: 64 data: data diff --git a/docs/fast.md b/docs/fast.md index c561c70..f59f5b5 100644 --- a/docs/fast.md +++ b/docs/fast.md @@ -7,407 +7,145 @@ +# Быстрый старт -# Быстрый старт (через скрипты) +> **Важно:** Обязательно проверяйте, есть ли сервис видеозвонков у вас в белых списках. Если его там нет - используйте другой. -> **Важно:** Обязательно проверяйте, есть ли сервис видеозвонков у вас в белых списках. Если его там нет - используйте другой. Список всех сервисов в белых списках скоро будет опубликован. +Этот способ запускает `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 ``` -speedtest - - - ---- - -## Шаг 2: Запустить сервер - -На машине, через которую должен идти трафик (VPS, сервер за рубежом, домашний ПК): +Бинарник появится в `build/`, например: ```sh -./script/srv.sh +./build/olcrtc-linux-amd64 ``` -#### Флаги `srv.sh` +## Сгенерировать ключ -| Флаг | Что делает | -|---|---| -| `--branch=` | Использовать другую ветку репозитория вместо `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 ``` -### Остановить +Обновление: ```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-`), они не конфликтуют между собой. - -```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). diff --git a/docs/manual.md b/docs/manual.md index 8a8494a..b1b7aa0 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -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) diff --git a/docs/settings.md b/docs/settings.md index 267b126..8e8375e 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -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 diff --git a/docs/uri.md b/docs/uri.md index c27b520..4e9f791 100644 --- a/docs/uri.md +++ b/docs/uri.md @@ -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 diff --git a/internal/app/session/session.go b/internal/app/session/session.go index 169a4b9..14b17ce 100644 --- a/internal/app/session/session.go +++ b/internal/app/session/session.go @@ -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 diff --git a/internal/app/session/session_test.go b/internal/app/session/session_test.go index 1e878a3..a75bb11 100644 --- a/internal/app/session/session_test.go +++ b/internal/app/session/session_test.go @@ -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}, }, }, { diff --git a/internal/client/client_test.go b/internal/client/client_test.go index 8402a5b..a96cc2b 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -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) diff --git a/internal/e2e/tunnel_test.go b/internal/e2e/tunnel_test.go index 972ef85..9f67ab3 100644 --- a/internal/e2e/tunnel_test.go +++ b/internal/e2e/tunnel_test.go @@ -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 } diff --git a/internal/engine/jitsi/jitsi.go b/internal/engine/jitsi/jitsi.go index 19b5728..1184490 100644 --- a/internal/engine/jitsi/jitsi.go +++ b/internal/engine/jitsi/jitsi.go @@ -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 // 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) diff --git a/internal/engine/jitsi/jitsi_test.go b/internal/engine/jitsi/jitsi_test.go index a22593e..4d1b770 100644 --- a/internal/engine/jitsi/jitsi_test.go +++ b/internal/engine/jitsi/jitsi_test.go @@ -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{ diff --git a/internal/muxconn/conn.go b/internal/muxconn/conn.go index aeef5f1..39c50e9 100644 --- a/internal/muxconn/conn.go +++ b/internal/muxconn/conn.go @@ -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++ { diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 9361bb9..21884ad 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -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 diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index 43032ea..2328cfa 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -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) diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 274a70b..a48f7c4 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -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) diff --git a/internal/transport/seichannel/transport.go b/internal/transport/seichannel/transport.go index 3f03f99..3b7178d 100644 --- a/internal/transport/seichannel/transport.go +++ b/internal/transport/seichannel/transport.go @@ -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 diff --git a/internal/transport/vp8channel/options.go b/internal/transport/vp8channel/options.go index d005e6f..1c55639 100644 --- a/internal/transport/vp8channel/options.go +++ b/internal/transport/vp8channel/options.go @@ -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 diff --git a/internal/transport/vp8channel/transport.go b/internal/transport/vp8channel/transport.go index 92ad2d3..c9e6b17 100644 --- a/internal/transport/vp8channel/transport.go +++ b/internal/transport/vp8channel/transport.go @@ -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 ) diff --git a/magefile.go b/magefile.go index 775c087..5d69562 100644 --- a/magefile.go +++ b/magefile.go @@ -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 // ───────────────────────────────────────────────────────────────────────────── diff --git a/mobile/mobile.go b/mobile/mobile.go index 055d33b..bbf52aa 100644 --- a/mobile/mobile.go +++ b/mobile/mobile.go @@ -708,7 +708,7 @@ func ensureDefaultConfigLocked() { transport: defaultTransport, dnsServer: defaultDNSServer, socksListenHost: defaultSocksHost, - vp8FPS: 60, + vp8FPS: 30, vp8BatchSize: 8, livenessInterval: control.DefaultInterval, livenessTimeout: control.DefaultTimeout, diff --git a/mobile/mobile_test.go b/mobile/mobile_test.go index 1a2f484..0d3a753 100644 --- a/mobile/mobile_test.go +++ b/mobile/mobile_test.go @@ -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 { diff --git a/readme.md b/readme.md index 399a82a..b312591 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/script/cnc.sh b/script/cnc.sh deleted file mode 100755 index 37df991..0000000 --- a/script/cnc.sh +++ /dev/null @@ -1,465 +0,0 @@ -#!/bin/bash - -echo "ЕСЛИ У ВАС ЕСТЬ ПРОБЛЕМЫ - Я В КУРСЕ, ПРОЕКТ В БЕТЕ, ПО ПРОБЛЕМАМ В ЧАТ t.me/openlibrecommunity ИЛИ ВООБЩЕ НЕКУДА, ЖДИТЕ РЕЛИЗА" - - -set -e - -PODMAN_ID=$(tr -dc 'a-z0-9' /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" <> "$CONFIG_FILE" <> "$CONFIG_FILE" <> "$CONFIG_FILE" <> "$CONFIG_FILE" <> "$CONFIG_FILE" <&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" <> "$config" <> "$config" <> "$config" <> "$config" <> "$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" <> "$config" <> "$config" - ;; -esac - -[ -n "$ffmpeg" ] && printf 'ffmpeg: "%s"\n' "$ffmpeg" >> "$config" - -exec /usr/local/bin/olcrtc "$config" diff --git a/script/docker/olcrtc-healthcheck.sh b/script/docker/olcrtc-healthcheck.sh deleted file mode 100644 index 1031cb7..0000000 --- a/script/docker/olcrtc-healthcheck.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -set -eu - -pidof olcrtc >/dev/null 2>&1 diff --git a/script/srv.sh b/script/srv.sh deleted file mode 100755 index 204f49e..0000000 --- a/script/srv.sh +++ /dev/null @@ -1,532 +0,0 @@ -#!/bin/bash - -echo "ЕСЛИ У ВАС ЕСТЬ ПРОБЛЕМЫ - Я В КУРСЕ, ПРОЕКТ В БЕТЕ, ПО ПРОБЛЕМАМ В ЧАТ t.me/openlibrecommunity ИЛИ ВООБЩЕ НЕКУДА, ЖДИТЕ РЕЛИЗА" - -set -e - -PODMAN_ID=$(tr -dc 'a-z0-9' /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" < "$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" <> "$CONFIG_FILE" <> "$CONFIG_FILE" <> "$CONFIG_FILE" <> "$CONFIG_FILE" <> "$CONFIG_FILE" </dev/null; then - TRANSPORT_PAYLOAD="" - else - TRANSPORT_PAYLOAD="" - 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 ""