2026-10-14 12:40:08 +07:00
fix
2026-10-03 20:51:26 +07:00
2026-10-14 12:40:08 +07:00
2026-10-14 12:40:08 +07:00
up
2026-10-12 14:28:09 +07:00
up
2026-10-13 15:30:11 +07:00
up
2026-10-13 15:30:11 +07:00
up
2026-10-13 14:16:35 +07:00
up
2026-10-10 15:58:44 +07:00
2026-10-03 13:45:42 +07:00
up
2026-10-13 15:30:11 +07:00
up
2026-10-13 15:30:11 +07:00

Вечерний детектив

Онлайн-детективная игра: команды проходят дело из точек, дверей, ключей и улик.

Структура workspace

Корневой репозиторий agents хранит рабочие инструкции и документацию. Код живёт в двух отдельных репозиториях:

У каждого из двух основных проектов своя история Git и свой remote. Не удаляйте вложенные .git и не смешивайте коммиты между репозиториями.

Локальный запуск

Требования: Go, Docker Compose, Node.js 20.19+ или 22.12+, npm.

Короткие команды из корня проекта:

make dev   # разработка: локальные БД/RustFS, Go-бэкенд и Vite-фронтенд
make game  # локальная игра: запуск venue с HTTPS из готовых артефактов
make stop  # остановка venue, данные сохраняются

make dev запускает приложения в текущем терминале; Ctrl+C останавливает оба приложения, БД и RustFS остаются запущены. Перед первым запуском подготовьте зависимости и .env по инструкции ниже; схема БД должна быть подготовлена через make -C evening_detective_server migrate-up (требуется Goose). make game — алиас make venue: перед первым запуском нужны сборка make prod-build, сертификат и локальный DNS, как описано в разделе площадки. make stop останавливает только площадку; процессы разработки не затрагивает.

1. Зависимости сервера

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 укажите:

VITE_API_URL=http://localhost:8090

2. Фронтенд

В другом терминале:

cd evening_detective_frontend
npm install
cp .env.example .env
npm run dev

Фронтенд будет доступен на http://localhost:5173.

3. Локальный пользователь

cd evening_detective_server
make dev-user

По умолчанию создаётся пользователь dev@example.com с паролем dev123456. Для изменения используйте DEV_USER_EMAIL и DEV_USER_PASSWORD.

Автономная площадка (ED-029)

venue/ — окружение для проведения игры на площадке без интернета. Локальная разработка описана выше и запускается отдельно.

После подготовки сертификата и локального DNS (инструкция ниже) из корня workspace запустите:

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; существующий файл настроек не перезаписывается. Аккаунт организатора при запуске синхронизируется с этими значениями.

После изменения кода обновите площадку, пока интернет доступен:

make prod-build
make venue

Сайт доступен по https://evening-detective.crabs-games.art; API и материалы обслуживаются тем же прокси. Интерфейс и API также можно открыть без локального DNS по http://<VENUE_BIND_IP>:<VENUE_PORT>. Ссылки на материалы, QR-коды и билеты при этом остаются на прежнем HTTPS-домене. Для подключения по LAN задайте постоянный IP ноутбука в VENUE_BIND_IP в venue/.env.

Приложение, база и RustFS подключены только к внутренней Docker-сети. Прокси дополнительно подключён к сети публикации порта. База и RustFS сохраняются в именованных томах. Миграции Goose выполняются до старта приложения. Для остановки без потери данных:

make stop

Не используйте 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:

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.

Блок «Wi-Fi площадки» (этапы 14–15) показывает текущие Wi-Fi-клиенты всего Cudy: MAC, IP, диапазон и доступный сигнал в dBm. Он не связывает MAC с UUID браузера или выбранной игрой, не показывает имена клиентов и трафик. Сборщик читает роутер каждые 10 секунд; при ошибке оставляет последний успешный снимок и его время, в том числе после перезапуска сборщика. Снимок старше 30 секунд помечается устаревшим; отсутствие сигнала отображается как «Нет данных». История не сохраняется.

Поддержан и проверен TR3000 V1 с прошивкой 2.5.27-20260804-183625, штатная английская HTML-страница клиентов. Неизвестная разметка возвращает ошибку, а не пустой список. После обновления прошивки нужно проверить адаптер снова. Настройки Cudy сборщик не меняет; использует только вход и чтение списка. Он работает отдельным процессом в сети entrance, без доступа к БД/RustFS; backend читает только общий том снимка, подключённый к нему read-only.

Сбор Cudy необязателен. Для включения создайте venue/cudy/ с правами 0700, сохраните пароль администратора в venue/cudy/password с правами 0600 и добавьте в локальный venue/.env:

CUDY_PASSWORD_FILE=/run/secrets/cudy/password
CUDY_URL=http://192.168.10.1

Пароль не записывается в .env, API, логи или Git; venue/cudy/ исключён из Git. URL допускает только числовой локальный IP без credentials и пути. После обновления артефактов запустите штатный make venue; отдельно сборщик backend бинарника запускается с --collect-cudy. Без CUDY_PASSWORD_FILE обращения к роутеру отсутствуют и панель сообщает, что сбор не настроен. Работающая площадка при реализации этапов 14–15 не обновлялась.

Разработчик без роли организатора/администратора/автора в 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:

    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. Перед игрой проверьте дату окончания сертификата:

    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 VENUE_PORT и TCP/UDP 53 из LAN в межсетевом экране ноутбука. VENUE_BIND_IP должен быть конкретным IPv4-адресом ноутбука, не 0.0.0.0: он используется и для публикации портов, и для DNS-ответов. Порты должны быть свободны. Для HTTPS менять APP_BASE_URL в .env не нужно: Compose-дополнение задаёт прежний HTTPS-домен интерфейса для QR-ссылок и материалов.

  6. Запустите:

    make venue
    

Команда проверяет доверие цепочке через системное хранилище OpenSSL, оба домена, срок действия и соответствие ключа до обращения к Docker. Ошибка не переключает работающее окружение на HTTP. Docker-образ приложения пересобирается при каждом запуске из готовых артефактов, без компиляции Go/Vue. При наличии всех базовых образов интернет не нужен. Доверие на реальных телефонах проверяется отдельно.

Интерфейс открывается по https://evening-detective.crabs-games.art, API — по https://evening-detective-api.crabs-games.art. Интерфейс использует свой /api/; прежний API-домен также обслуживается локально. На VENUE_PORT интерфейс и API доступны по IP через HTTP без редиректа; запросы по доменным именам на порту 80 перенаправляются на 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 до настройки клиентов:

python3 tests/venue/dns.py --server 192.168.10.155

После продления сертификата с доступным интернетом замените оба PEM-файла и повторите make venue: Nginx проверяет конфигурацию и перечитывает сертификат. Продление не выполняется автоматически. После обновления кода выполните make prod-build, затем make venue; контейнеры всегда перезапускаются с HTTPS.

Для остановки HTTPS-окружения с сохранением данных:

docker compose -f venue/docker-compose.yml -f venue/docker-compose.https.yml \
  --env-file venue/.env down

Проверка после настройки DNS и запуска (первый smoke создаёт тестовые данные):

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. До их завершения используйте только тестовые игры.

Проверки перед изменениями

Сервер:

cd evening_detective_server
make test

Фронтенд:

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 одной командой:

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/.

Для сборки артефактов обоих окружений используйте:

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 и инструкции целевого проекта.
  2. Делайте минимальное изменение в одном дочернем репозитории.
  3. Запускайте релевантные проверки.
  4. Коммиты и push выполняет только пользователь; агент никогда не коммитит и не отправляет изменения.

Текущие подробные инструкции:

S
Description
No description provided
Readme 61 MiB
Languages
Python 75.2%
HTML 10.5%
JavaScript 8.4%
Shell 3%
CSS 1.9%
Other 1%