# Вечерний детектив Онлайн-детективная игра: команды проходят дело из точек, дверей, ключей и улик. ## Структура 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=` возвращает сгруппированные устройства организатору, администратору или разработчику. Фильтр необязателен; при его использовании список содержит только подключения выбранной игры, без непривязанных браузеров. Вкладка «Состояние» (`/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) — детали бэкенда.