Files
agents/README.md
T
2026-10-11 14:17:03 +07:00

376 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Вечерний детектив
Онлайн-детективная игра: команды проходят дело из точек, дверей, ключей и улик.
## Структура workspace
Корневой репозиторий `agents` хранит рабочие инструкции и документацию. Код живёт
в двух отдельных репозиториях:
- [`evening_detective_server/`](evening_detective_server/) — Go-бэкенд;
- [`evening_detective_frontend/`](evening_detective_frontend/) — Vue/TypeScript-фронтенд;
- [`email_server/`](email_server/) — старый собственный почтовый сервер, не нужен для локального запуска.
У каждого из двух основных проектов своя история Git и свой remote. Не удаляйте
вложенные `.git` и не смешивайте коммиты между репозиториями.
## Локальный запуск
Требования: Go, Docker Compose, Node.js 20.19+ или 22.12+, npm.
### 1. Зависимости сервера
```sh
cd evening_detective_server
docker compose -f docker-compose-db.yml up -d
cp .env.example .env
make run
```
Сервис запускает:
- gRPC на `http://localhost:8080`;
- REST gateway и Swagger на `http://localhost:8090`.
Для локального фронтенда в `evening_detective_frontend/.env` укажите:
```dotenv
VITE_API_URL=http://localhost:8090
```
### 2. Фронтенд
В другом терминале:
```sh
cd evening_detective_frontend
npm install
cp .env.example .env
npm run dev
```
Фронтенд будет доступен на `http://localhost:5173`.
### 3. Локальный пользователь
```sh
cd evening_detective_server
make dev-user
```
По умолчанию создаётся пользователь `dev@example.com` с паролем `dev123456`.
Для изменения используйте `DEV_USER_EMAIL` и `DEV_USER_PASSWORD`.
## Автономная площадка (ED-029)
`venue/` — окружение для проведения игры на площадке без интернета.
Локальная разработка описана выше и запускается отдельно.
После подготовки сертификата и локального DNS (инструкция ниже) из корня workspace запустите:
```sh
make prod-build
make venue
```
`make prod-build` заранее готовит артефакты для `prod/` и `venue/`.
Деплой не компилирует Go/Vue и не устанавливает зависимости исходного кода.
Если артефактов нет, запуск завершится с предложением выполнить `make prod-build`.
Первый запуск с интернетом сам создаёт настройки и случайные секреты в
`venue/.env`, готовит Docker-образы из готовых файлов, запускает миграции и создаёт организатора.
Каждый `make venue` пересобирает Docker-образ из готовых файлов и принудительно
пересоздаёт контейнеры с HTTPS. При наличии runtime-образов и Alpine запуск
работает без интернета; отсутствующие образы скачиваются при первой подготовке.
Email и пароль для входа указаны в `VENUE_USER_EMAIL` и `VENUE_USER_PASSWORD`
в `venue/.env`; существующий файл настроек не перезаписывается.
Аккаунт организатора при запуске синхронизируется с этими значениями.
После изменения кода обновите площадку, пока интернет доступен:
```sh
make prod-build
make venue
```
Сайт доступен по `https://evening-detective.crabs-games.art`; API и материалы
обслуживаются тем же прокси. Для подключения по LAN задайте постоянный IP ноутбука
в `VENUE_BIND_IP` в `venue/.env` и настройте локальный DNS, как описано ниже.
Приложение, база и RustFS подключены только к внутренней Docker-сети.
Прокси дополнительно подключён к сети публикации порта.
База и RustFS сохраняются в именованных томах.
Миграции Goose выполняются до старта приложения. Для остановки без потери данных:
```sh
docker compose -f venue/docker-compose.yml -f venue/docker-compose.https.yml --env-file venue/.env down
```
Не используйте `down -v`: это удаляет данные. После изменений кода или миграций
выполните `make venue`, пока интернет доступен. Секреты, сборка,
сертификаты и пакеты игры игнорируются Git в `venue/`.
`APP_MODE=venue` отключает SMTP, фоновые письма участия/отзывов и внешний webhook
регистрации. Аккаунты готовятся через bootstrap; почтовая регистрация аккаунта
и сброс пароля возвращают явную ошибку без изменения данных. Команды создаёт
организатор на странице игры; участие подтверждается по существующим правилам:
при создании в последние 24 часа до игры или при первом игровом действии.
Для дополнительных операторов используйте отдельные локальные email
и пароли в `.env` и снова выполните `make venue` (старые аккаунты сохраняются).
Используйте только тестовые игры до реализации возврата результатов онлайн.
Проверки для разработчика находятся в `tests/venue/` и не нужны для запуска
площадки. `deploy.py` проверяет сценарии запуска без Docker; `smoke.py` проверяет
работающее окружение и создаёт тестовый сценарий, игру, команду и материал через API:
```sh
python3 tests/venue/deploy.py
python3 tests/venue/smoke.py
make venue
python3 tests/venue/smoke.py --verify
```
Последняя команда проверяет ранее созданные записи и файл после перезапуска.
Тестовые данные остаются в локальной базе; повторное создание запускайте только
когда нужен новый набор. Дождитесь ответа `/api/test/ping` перед проверкой.
### Учёт устройств (этап 12)
Venue автоматически учитывает браузеры игроков, организаторов и экранов:
`POST /api/devices/heartbeat` публично регистрирует подключение, а
`GET /api/devices?gameID=<ID>` возвращает сгруппированные устройства организатору,
администратору или разработчику. Фильтр необязателен; при его использовании
список содержит только подключения выбранной игры, без непривязанных браузеров.
Вкладка «Состояние» (`/games/:id?tab=state`, этап 13) доступна в venue
организатору, администратору и разработчику. Она показывает число доступных
устройств, устройств без связи и подключений, UUID профиля и последнее
обращение; раскрытие устройства показывает тип подключения, ID команды/экрана,
статус, IP и User-Agent. Устройства без связи идут первыми, далее сортировка
по UUID. Статусы и время обновления берутся из серверного снимка.
Снимок запрашивается сразу и раз в 5 секунд только на активной вкладке,
без параллельных запросов; поздние ответы после ухода игнорируются.
При ошибке предыдущий снимок и время остаются видимыми, доступен повтор.
«Нет связи» означает отсутствие heartbeat сайта, а не диагностику Wi-Fi.
Разработчик без роли организатора/администратора/автора в venue получает
список игр и только вкладку состояния: сведения игры ограничены ID, названием,
описанием, форматом, местом, плановым началом и статусом. Команды, пароли,
сценарий, материалы и игровой прогресс не выдаются и не загружаются интерфейсом.
Создание игр и результаты скрыты; существующий импорт сохраняется.
Heartbeat разработчика остаётся браузерным без контекста организатора.
В prod этот доступ и вкладка состояния не открываются.
UUID браузерного профиля хранится отдельно от авторизации в localStorage;
каждая вкладка и перезагрузка страницы создают новый UUID подключения.
Без localStorage устройство получает временный UUID. Контекст страницы
полностью заменяется при heartbeat и очищается при навигации. Организатор
проверяется по JWT и правам игры, команда — по паролю и неотменённому участию,
экран — по существующей сессии; игру команды и экрана определяет сервер.
Credentials используются только для проверки и не сохраняются в реестре.
Heartbeat отправляется сразу и каждые 20 секунд без параллельных запросов,
а также при возвращении вкладки и восстановлении сети. Ошибки не блокируют
игру и не показывают toast; повтор выполняется по интервалу. В prod ответ
содержит `enabled=false`, после чего браузер прекращает heartbeat;
список устройств в prod недоступен.
Реестр хранится только в памяти backend: UUID, серверные времена,
последние IP/User-Agent и проверенные ID игры/команды/экрана. IP не участвует
в идентификации и авторизации. Связь актуальна строго менее 60 секунд;
устройство доступно, если доступно хотя бы одно его подключение в списке.
Подключения удаляются через 15 минут отсутствия при heartbeat или чтении.
Предел — 1024 подключения; новые отклоняются с `ResourceExhausted`, если
после очистки нет места. После перезапуска открытые браузеры регистрируются
заново. Времена устройства вычисляются по сохранённым подключениям.
«Нет связи» означает отсутствие heartbeat сайта, а не отключение от Wi-Fi.
UUID обозначает браузерный профиль, а не физический телефон; очистка
localStorage создаёт новое устройство. История посещений, полный URL,
имена игроков, email и credentials в список не попадают.
### Прежние домены и HTTPS (этап 3)
Подготовка в репозитории готова; проверка реального Cudy и Android/iPhone без WAN
ещё требуется. `make venue` всегда запускает HTTPS с пересборкой Docker-образа
и перезапуском контейнеров. Для первого запуска:
1. Пока интернет доступен, подготовьте артефакты через `make prod-build`.
Запуск `make venue` выполните после подготовки сертификата и локального DNS.
2. Выпустите отдельный доверенный сертификат с новым ключом для двух имён:
`evening-detective.crabs-games.art` и `evening-detective-api.crabs-games.art`.
Используйте DNS-01: проверяются TXT-записи `_acme-challenge` в публичном DNS;
публичные A/AAAA-записи production менять не нужно. Не копируйте ключ production.
Например, на машине с установленным Certbot:
```sh
certbot certonly --manual --preferred-challenges dns \
--cert-name evening-detective-venue \
-d evening-detective.crabs-games.art \
-d evening-detective-api.crabs-games.art
```
Следуйте подсказкам Certbot для TXT-записей. Выпуск сертификата выполняется
отдельно от приложения; Certbot не входит в runtime площадки.
3. Скопируйте полную цепочку (сертификат сервера и промежуточные сертификаты) в
`venue/certificates/fullchain.pem`, незашифрованный закрытый ключ — в
`venue/certificates/privkey.pem`. Ограничьте доступ к каталогу и ключу владельцем
(режимы 700 и 600); процесс Nginx в контейнере должен иметь доступ на чтение.
Каталог игнорируется Git. Перед игрой проверьте дату окончания сертификата:
```sh
openssl x509 -in venue/certificates/fullchain.pem -noout -dates
```
4. На Cudy вручную закрепите IP ноутбука в **Advanced Settings → IP/MAC Binding**.
На TR3000 с прошивкой 2.5.27 DE в прочитанных разделах Custom DNS и DHCP нет
формы статических записей. Поэтому venue запускает отдельный CoreDNS на ноутбуке.
В **Advanced Settings → DHCP Server** задайте **Preferred DNS = IP ноутбука**,
**Alternate DNS** оставьте пустым. Переподключите телефоны к Wi-Fi, чтобы они
получили новые параметры DHCP. Custom DNS роутера менять не требуется.
Проверьте клиентскую изоляцию и настройки Private DNS/DoH устройств: запросы
двух доменов должны приходить на локальный DNS, а клиенты — достигать ноутбука.
Скрипты проекта не меняют роутер и не прошивают его.
5. В `venue/.env` задайте `VENUE_BIND_IP=<постоянный-IP-ноутбука>` и разрешите
входящие TCP 80/443 и TCP/UDP 53 из LAN в межсетевом экране ноутбука.
`VENUE_BIND_IP` должен быть конкретным IPv4-адресом ноутбука, не `0.0.0.0`:
он используется и для публикации портов, и для DNS-ответов. Порты должны быть свободны.
Для HTTPS менять `APP_BASE_URL` в `.env` не нужно: Compose-дополнение задаёт
прежний HTTPS-домен интерфейса для QR-ссылок и материалов.
6. Запустите:
```sh
make venue
```
Команда проверяет доверие цепочке через системное хранилище OpenSSL, оба домена,
срок действия и соответствие ключа до обращения к Docker. Ошибка не переключает
работающее окружение на HTTP. Docker-образ приложения пересобирается при каждом
запуске из готовых артефактов, без компиляции Go/Vue. При наличии всех базовых
образов интернет не нужен. Доверие на реальных телефонах проверяется отдельно.
Интерфейс открывается по `https://evening-detective.crabs-games.art`, API — по
`https://evening-detective-api.crabs-games.art`. Интерфейс использует свой `/api/`;
прежний API-домен также обслуживается локально. HTTP на портах 80 и прежнем
`VENUE_PORT` перенаправляется на HTTPS с сохранением пути и параметров.
CoreDNS публикуется только на `VENUE_BIND_IP:53` по UDP/TCP. Оба домена получают
этот IPv4 локально; AAAA-запросы не получают внешние адреса. Для них интернет
не требуется. Остальные домены пересылаются на 1.1.1.1/8.8.8.8 и работают при
доступном интернете; их недоступность без WAN не мешает локальным доменам.
DNS подключён к сети публикации портов и не имеет доступа к внутренней сети
backend/БД. Образ CoreDNS скачивается при первой подготовке и используется
повторно без скачивания. При изменении IP ноутбука обновите `VENUE_BIND_IP`,
настройки DHCP Cudy и повторите `make venue`.
Проверка DNS до настройки клиентов:
```sh
python3 tests/venue/dns.py --server 192.168.10.155
```
После продления сертификата с доступным интернетом замените оба PEM-файла и
повторите `make venue`: Nginx проверяет конфигурацию и перечитывает сертификат.
Продление не выполняется автоматически. После обновления кода выполните
`make prod-build`, затем `make venue`; контейнеры всегда перезапускаются с HTTPS.
Для остановки HTTPS-окружения с сохранением данных:
```sh
docker compose -f venue/docker-compose.yml -f venue/docker-compose.https.yml \
--env-file venue/.env down
```
Проверка после настройки DNS и запуска (первый smoke создаёт тестовые данные):
```sh
python3 tests/venue/smoke.py
python3 tests/venue/smoke.py --verify
```
TLS-проверка в smoke включена; самоподписанный сертификат и обход предупреждений
не подходят для приёмки. На Android и iPhone отключите WAN и мобильные данные,
подключитесь к Wi-Fi площадки и проверьте DNS обоих доменов, отсутствие TLS-ошибок,
вход организатора, материалы и QR-ссылки тестовой игры. Повторите после перезапуска.
Проверьте запрос доступа к камере отдельно: распознавание QR в текущем интерфейсе
также требует поддержки браузером `BarcodeDetector`.
Этап 3 сохраняет адреса, но не переносит данные production. Production-QR может
вести на игру или билет, которых в локальной базе ещё нет; перенос и возврат
данных относятся к этапам 4–11. До их завершения используйте только тестовые игры.
## Проверки перед изменениями
Сервер:
```sh
cd evening_detective_server
make test
```
Фронтенд:
```sh
cd evening_detective_frontend
npm run type-check
npm run lint
npm run lint:css
```
При изменении protobuf сначала обновляйте сгенерированный код существующими
командами `make generate` на сервере и `npm run proto:gen` на фронтенде.
Сгенерированные файлы руками не редактируйте.
## Сборка и публикация
Из корня workspace можно собрать и опубликовать root, backend и frontend одной
командой:
```sh
make publish MSG="Исправить подсчёт ходов в игре"
```
Команда вызывает локальные build-команды репозиториев, создаёт коммит с
указанным сообщением и выполняет `git push`.
Общая команда собирает бэкенд один раз и сохраняет артефакты в обе папки:
`prod/` и `venue/`. Обе используют `backend/evening_detective_server`,
`frontend/dist/` и `migrations/`; Compose называется `docker-compose.yml`,
образ бэкенда описан в `Dockerfile.backend`. Фронтенд собирается отдельно для
каждого окружения: production сохраняет текущие Vite-настройки, venue использует
API через свой прокси (`VITE_API_URL=/.`). Локальные Goose и SQL подготовки
аккаунта лежат в `venue/backend/`.
Для сборки артефактов обоих окружений используйте:
```sh
make build
```
`make prod-build` выполняет ту же общую сборку. `make venue` пересобирает
Docker-образ из готовых артефактов и перезапускает площадку с HTTPS.
`make venue-update` и `make venue-https` сохранены как алиасы `make venue`. Команды
обновляют только генерируемые артефакты в `prod`; конфигурация `prod` не меняется.
## Окружение и секреты
Файлы `.env` предназначены только для локальных значений и не коммитятся.
Начинайте настройку с `.env.example`. Секреты SMTP, JWT, S3, webhook и внешних
API храните только в окружении или секрет-хранилище.
Публичный адрес приложения задаётся через `APP_BASE_URL`; он используется в
ссылках из писем и QR-кодах команд.
## Рабочий процесс
1. Перед изменением прочитайте корневой [`AGENTS.md`](AGENTS.md) и инструкции
целевого проекта.
2. Делайте минимальное изменение в одном дочернем репозитории.
3. Запускайте релевантные проверки.
4. Коммиты и push выполняет только пользователь; агент никогда не коммитит и не отправляет изменения.
Текущие подробные инструкции:
- [`AGENTS.md`](AGENTS.md) — правила workspace;
- [`evening_detective_frontend/AGENTS.md`](evening_detective_frontend/AGENTS.md) — правила фронтенда;
- [`evening_detective_server/README.md`](evening_detective_server/README.md) — детали бэкенда.