# Деплой AGIL Calls — calls.guardify.ru

Запись о фактическом развёртывании тестового контура (13.11.2026).
Проверено: HTTPS-страницы, API, admin key, два участника с видео и чатом.

## 1. Куда развёрнуто и почему

| Параметр | Сервер 2 (msk.guardify.ru) | Сервер 3 (guardify.ru) — **выбран** |
|---|---|---|
| IP | 185.212.148.236 | **95.182.94.7** |
| RAM | 709 МБ (свободно ~341 МБ) | **1968 МБ (свободно ~1.2 ГБ + 4 ГБ swap)** |
| Диск | 18 ГБ (свободно 15 ГБ) | **19 ГБ (свободно ~8.8 ГБ после сборки)** |
| 80/443 | заняты существующим стеком | заняты существующим стеком (HAProxy+Caddy) |
| Статус | **не разворачивали** — не хватит памяти на сборку/запас | **развёрнуто** |

Выбор: на Сервере 3 больше свободной RAM (критично для сборки Node-образа) и
уже настроен домен `calls.guardify.ru` (DNS → 95.182.94.7, сертификат Caddy,
SNI в HAProxy). Диск обоих серверов достаточен; на Сервере 2 остаётся ~341 МБ
RAM — риск OOM, поэтому туда не ставили (вариант — после увеличения RAM
развернуть sidecar: MCP/docs или второй инстанс).

## 2. Публичные адреса

| Что | URL |
|---|---|
| Демо-хост (создать комнату) | https://calls.guardify.ru/ |
| Встраиваемая страница звонка | https://calls.guardify.ru/embed.html |
| Панель разработчика | https://calls.guardify.ru/dashboard/ |
| Визуальный редактор | https://calls.guardify.ru/editor/ |
| API Explorer (офлайн) | https://calls.guardify.ru/docs |
| OpenAPI (публичный / полный) | https://calls.guardify.ru/openapi.json · /openapi.admin.json |
| SDK | https://calls.guardify.ru/sdk/agil-calls.js |
| Health | https://calls.guardify.ru/v1/health |

## 3. Схема на сервере

```
Internet :443 ─► HAProxy (host, SNI: calls.guardify.ru → caddy_https)
                    │ proxy-protocol
                    ▼
              Caddy 127.0.0.1:9443 (host; TLS Let's Encrypt для calls.guardify.ru)
                    │ reverse_proxy
                    ▼
        127.0.0.1:8787  Docker: agil-calls (Node 23, non-root, healthcheck, mem_limit 768m)
                    │ volume agil-calls_agil-data → /app/.data
                    ▼
        coturn (host network, UDP 3478, relay 49200-49250, relay-ip 95.182.94.7)
```

- HAProxy ACL `calls.guardify.ru` добавлен в `/opt/3x-ui/haproxy.cfg` (бэкап
  рядом, файл `haproxy.cfg.bak-20261113-115939`), контейнер перезапущен.
- Caddy: блоки `calls.guardify.ru:80` (redirect) и
  `https://calls.guardify.ru:9443` (reverse_proxy на 127.0.0.1:8787) в
  `/opt/3x-ui/Caddyfile` (бэкап `Caddyfile.bak-20261113-115939`), reload без
  простоя. Сертификат получен автоматически (HTTP-01).
- Приложение слушает только `127.0.0.1:8787` (порт наружу не опубликован).

## 4. Файлы и секреты на сервере

| Путь | Что |
|---|---|
| `/opt/agil-calls` | исходники, `docker-compose.yml`, `Dockerfile`, `build.log` |
| `/opt/agil-calls/.env` | секреты и настройки (chmod 600): токен-секрет, API-ключ, TURN-пароль, ICE, origin |
| `/root/agil-admin-key.txt` | API-ключ тенанта `demo` (chmod 600) — для `/dashboard/` и admin API |
| `/opt/3x-ui/Caddyfile`, `/opt/3x-ui/haproxy.cfg` | прокси (есть бэкапы `*.bak-20261113-115939`) |
| Docker volume `agil-calls_agil-data` | `.data`: конфиги тенантов, аудит, файлы, записи |

**Получить ключ:** `ssh root@95.182.94.7 'cat /root/agil-admin-key.txt'`.
В панели `/dashboard/` на входе: server URL `https://calls.guardify.ru`,
API-ключ из файла. **Ротировать:** изменить `AGIL_API_KEYS` в `.env`,
`docker compose up -d`, обновить файл ключа.

## 5. Настройки (.env, ключевое)

```dotenv
AGIL_PUBLIC_ORIGIN=https://calls.guardify.ru
AGIL_ALLOW_ANONYMOUS=1          # демо: любой может создать комнату тенанта demo
AGIL_TRUST_PROXY=1              # за HAProxy/Caddy
AGIL_DOCS_ENABLED=1             # /docs и OpenAPI публичны
AGIL_ADMIN_API_ENABLED=1
AGIL_ICE_SERVERS=[...stun:stun.l.google.com:19302, turn:95.182.94.7:3478...]
TURN_EXTERNAL_IP=95.182.94.7
```

Секреты сгенерированы `openssl rand -hex`; значения не коммитятся в git.

## 6. Проверки (факт)

| Проверка | Результат |
|---|---|
| `GET https://calls.guardify.ru/v1/health` | 200, `docsEnabled=true` |
| Страницы `/`, `/embed.html`, `/dashboard/`, `/editor/`, `/docs`, `/openapi.json`, `/openapi.admin.json`, `/sdk/agil-calls.js` | 200 |
| TLS `calls.guardify.ru` | сертификат Let's Encrypt получен и валиден |
| `GET /v1/admin/overview` с ключом | 200 (`totalRoomsCreated` растёт) |
| `node scripts/remote-check.mjs --url https://calls.guardify.ru` | **17/17**: 2 участника, 2 плитки, удалённый видео-кадр, тема, чат доставлен, 0 JS-ошибок |
| `POST /v1/rooms` (anonymous) | 200, `joinUrl`/`embedUrl` на домене |
| Docker | `unless-stopped`, health `healthy`, Docker включён в автозапуск |
| TURN | слушает `95.182.94.7:3478/udp`, relay 49200–49250 |

## 7. Эксплуатация

```bash
ssh root@95.182.94.7
cd /opt/agil-calls
docker compose ps                 # статус
docker compose logs -f agil-calls # логи
docker compose restart agil-calls # рестарт
df -h / ; free -m ; docker system df

# обновление кода:
#   локально: tar → scp в /opt/agil-calls → docker compose build agil-calls && docker compose up -d
# бэкап данных (volume):
docker run --rm -v agil-calls_agil-data:/data -v /opt/backups:/backup alpine \
  tar czf /backup/agil-data-$(date +%F).tgz -C /data .

# выключить публичное демо/доки (после тестов):
sed -i 's/^AGIL_ALLOW_ANONYMOUS=.*/AGIL_ALLOW_ANONYMOUS=0/' /opt/agil-calls/.env
sed -i 's/^AGIL_DOCS_ENABLED=.*/AGIL_DOCS_ENABLED=0/' /opt/agil-calls/.env
cd /opt/agil-calls && docker compose up -d
```

## 8. Ограничения и риски тестового контура

- Один узел: комнаты/чат в памяти; рестарт контейнера завершает активные звонки.
- Mesh ≤8 участников (SFU — в roadmap); для больших групп нужен LiveKit/mediasoup.
- TURN: UDP 3478 и relay 49200–49250 должны быть открыты у провайдера; если
  фаервол режет UDP — звонки пойдут только при прямом соединении (STUN).
- Публичное демо (`AGIL_ALLOW_ANONYMOUS=1`) и публичные docs — только для
  тестового стенда; для продакшена включить токены/ключи и закрыть docs.
- Инфраструктура HAProxy/Caddy на сервере общая с VPN-стеком: HAProxy был
  перезапущен (≈1–2 с простоя активных VPN-сессий); конфиги забэкаплены.
- Мониторинг/алерты и бэкапы по расписанию не настроены — добавить при
  переводе в прод (см. `docs/deployment.md`).

## 9. Откат

1. Прокси: восстановить бэкапы Caddy/HAProxy и перезагрузить:
   `cp /opt/3x-ui/Caddyfile.bak-20261113-115939 /opt/3x-ui/Caddyfile` +
   `docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile`;
   `cp /opt/3x-ui/haproxy.cfg.bak-20261113-115939 /opt/3x-ui/haproxy.cfg && docker restart haproxy`.
2. Приложение: `cd /opt/agil-calls && docker compose down` (данные остаются в volume).
3. Полное удаление: `docker compose down -v` (удалит `.data` — только после бэкапа).
