This commit is contained in:
2026-10-11 15:00:22 +07:00
parent 2b7b95fe2b
commit f25c48b57b
8 changed files with 222 additions and 6 deletions
+30
View File
@@ -152,6 +152,36 @@ Venue автоматически учитывает браузеры игрок
При ошибке предыдущий снимок и время остаются видимыми, доступен повтор.
«Нет связи» означает отсутствие 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`:
```dotenv
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, названием,
описанием, форматом, местом, плановым началом и статусом. Команды, пароли,
+6 -3
View File
@@ -27,13 +27,13 @@
### ED-029 — Автономная площадка и панель организатора
- Статус: `IN_PROGRESS` — этап 3 принят пользователем без WAN; этапы 4–13 завершены; аппаратная приёмка этапа 8 подтверждена пользователем.
- Статус: `IN_PROGRESS` — этап 3 принят пользователем без WAN; этапы 4–14 завершены; автоматическая часть этапа 15 завершена, аппаратная приёмка остаётся; этап 8 принят пользователем.
- Репозитории: корневой, `evening_detective_server`, `evening_detective_frontend`.
- Цель: реализовать принятый пользователем план из 15 последовательных этапов: автономное окружение; локальные организаторы; прежние адреса через Cudy; пакет игры; экспорт со статусом офлайн-режима; локальный импорт; интерфейс передачи; полный билетный и игровой flow; пакет результатов; возврат онлайн; интерфейс возврата; устройства; панель состояния; сбор данных Cudy; Wi-Fi и испытание площадки.
- Критерии: проверяемый результат каждого этапа; цикл «онлайн → ноутбук → проведение → онлайн» через интерфейс с сохранением QR, билетов и связей; повторные импорты без дублей; мониторинг устройств и Wi-Fi; полный прогон без WAN, 100 клиентов и проверка покрытия.
- Ограничения: только тестовые игры до завершения возврата; существующие правила игры и формат `offline` сохраняются; без несогласованных новых зависимостей и отклонений от стандартов; без персональных списков игроков, CPU/памяти Cudy и автоматического переключения серверов; настройки роутера менять только по явному запросу пользователя; секреты и пакеты вне Git; commit/push выполняет пользователь.
- Проверка: Go-стандарт и `go test ./...`; PostgreSQL-интеграционные тесты для атомарности; frontend-стандарт, type-check, lint, lint:css и build; штатная генерация protobuf; ручные контрольные точки после этапов 3, 8, 11, 15.
- Прогресс: этапы 1–2 реализованы и проверены в локальном Docker-окружении. Добавлены отдельный Compose, подготовка образов заранее, постоянные тома, миграции перед стартом, healthchecks и прокси. Явный `APP_MODE=venue` отключает SMTP, фоновые письма и внешний webhook; почтовые операции аккаунта возвращают безопасную ошибку до изменения данных. Локальные аккаунты создаются штатным `dev-user.sql`, роли и правила автоматического подтверждения команд сохраняются. Этап 3 принят пользователем после проверки без WAN; формат и код пакета игры (этап 4) реализованы и проверены. Экспорт со статусом офлайн-режима (этап 5) и локальный импорт (этап 6) реализованы и проверены. Этап 7 реализован и проверен; этап 8 проверен на изолированном стенде и принят пользователем на аппаратной контрольной точке; этапы 9–13 реализованы и проверены; этапы 14–15 ещё не завершены.
- Прогресс: этапы 1–2 реализованы и проверены в локальном Docker-окружении. Добавлены отдельный Compose, подготовка образов заранее, постоянные тома, миграции перед стартом, healthchecks и прокси. Явный `APP_MODE=venue` отключает SMTP, фоновые письма и внешний webhook; почтовые операции аккаунта возвращают безопасную ошибку до изменения данных. Локальные аккаунты создаются штатным `dev-user.sql`, роли и правила автоматического подтверждения команд сохраняются. Этап 3 принят пользователем после проверки без WAN; формат и код пакета игры (этап 4) реализованы и проверены. Экспорт со статусом офлайн-режима (этап 5) и локальный импорт (этап 6) реализованы и проверены. Этап 7 реализован и проверен; этап 8 проверен на изолированном стенде и принят пользователем на аппаратной контрольной точке; этапы 9–13 реализованы и проверены; этап 14 реализован и проверен на настоящем TR3000; код и автоматические проверки этапа 15 завершены, ожидается аппаратная приёмка Wi-Fi.
- Дополнение по запросу пользователя: структура `venue` приведена к `prod` (`backend/`, `frontend/dist/`, `migrations/`, `Dockerfile.backend`, `docker-compose.yml`, `deploy.sh`); имена основных Compose-сервисов совпадают. Общая генерация `make build` / `make prod-build` готовит артефакты обеих папок; frontend собирается отдельно с настройками своего окружения. Текущие файлы `prod` при этой работе не изменяются; сборка проверяется с production-выводом в `/tmp`.
- Упрощение запуска: отдельные `init.sh` и `prepare.sh` удалены, всё собрано в `deploy.sh`. Единственная команда запуска — `make venue`: создаёт `.env` только при его отсутствии, при необходимости готовит зависимости/образы и автоматически создаёт организатора. Повторный готовый запуск не собирает и не скачивает образы. `make venue-update` явно пересобирает окружение с интернетом. Аккаунт организатора синхронизируется со значениями `.env` при каждом запуске. Первый запуск, повторный запуск без интернета и обновление проверяются `python3 tests/venue/deploy.py` с подставными командами; реальный готовый запуск проверен через Docker и API-smoke.
- Настройки площадки: по запросу пользователя необязательный `venue/.env.example` удалён; настройки создаются автоматически через `make venue`. Убраны ссылка и проверка примера; `python3 tests/venue/deploy.py` прошёл. Существующая `.env` не изменялась.
@@ -84,7 +84,10 @@
- Результат этапа 13 (2026-10-11): venue-вкладка «Состояние» `/games/:id?tab=state` показывает серверные connected/serverTime, количество доступных устройств/устройств без связи/подключений, UUID профиля, последние обращения и раскрываемые подключения с типом, ID команды/экрана, статусом, IP и User-Agent. Нет связи идёт первым, затем UUID; непривязанные браузеры исключает фильтр игры. Generated GetDevices вызывается сразу и каждые 5 секунд через useSimplePolling только на активной вкладке, без параллельных запросов; поздние ответы после смены игры, ухода и уничтожения игнорируются. Первичная загрузка, пустой список, встроенная ошибка, сохранение предыдущего снимка/времени и повтор реализованы без повторных toast. Вкладка доступна с клавиатуры. Игровые показатели, Cudy и история присутствия не добавлялись.
- Доступ этапа 13: GetGames/GetGame и UI permissions расширены для разработчика только в venue. Разработчик без организатора/администратора/автора получает явный whitelist ID/названия/описания/формата/места/планового начала/статуса; GetGame использует краткое чтение без сценария/команд. UI скрывает создание и результаты, показывает название и только состояние, нормализует другие tab, не загружает экраны/музыку/план/карту/ответы; heartbeat остаётся браузерным. Дополнительные роли, импорт и запреты игровых действий сохранены; prod новые права не получает. При смене роли полный поздний GetGame не применяется, приватные баннер/модалки скрываются.
- Проверки этапа 13: gofmt, полный go test ./..., race-тесты device_presence_service/app с управляемыми часами этапа 12; frontend type-check/lint/lint:css/build и self-check gameState прошли. Self-check покрывает сводку/сортировку, активность, отсутствие параллельных запросов, поздние ответы, ошибку/восстановление. Изолированные PostgreSQL/RustFS API-регрессии и Firefox проверили организатора, три подключения одного профиля, клавиатуру, смену контекста, пустой список, потерю/восстановление GetDevices, остановку запросов на другой вкладке, разработчика venue/prod, безопасные GetGame/GetGames и отсутствие закрытых фоновых запросов. API/protobuf/миграции/зависимости не менялись. Ручное ревью diff по Go/frontend-стандартам и независимое read-only ревью выполнены; пользовательские изменения prod/reproxy/backend-бинарника сохранены. Работающие окружения и Cudy не менялись.
- Следующий шаг: этап 14 — сбор данных Cudy. Этап 13 завершён; общая задача остаётся `IN_PROGRESS` и не переносится в архив. ZIP остаётся неподписанным. Фактический ZIP 1 ГиБ, нагрузка на 100 клиентов и покрытие Wi-Fi не проверялись; развёртывание, commit/push не выполнялись.
- Результат этапа 14 (2026-10-11): отдельный процесс того же backend-бинарника `--collect-cudy` читает текущих Wi-Fi-клиентов Cudy через штатный вход и GET страницы клиентов раз в 10 секунд. Проверен настоящий TR3000 V1 2.5.27-20260804-183625; настройки не менялись. Собираются только MAC/IP/диапазон/доступный сигнал dBm, без имён, трафика и истории. Пароль читается из локального файла, не выдаётся API/логами; `venue/cudy/` исключён из Git. URL ограничен числовым private/loopback IP, redirects тем же host/scheme, ответ 2 МиБ, HTTP timeout 5 секунд, сессия переиспользуется и восстанавливается. Неизвестная разметка вызывает ошибку вместо ложного пустого списка. Collector изолирован в entrance без БД/RustFS; backend сохраняет внутреннюю сеть и читает общий атомарный снимок read-only. Без credential-файла обращений к роутеру нет.
- Результат автоматической части этапа 15: Wi-Fi-блок вкладки состояния показывает весь Cudy независимо от игры, MAC/IP/диапазон/dBm или «Нет данных», число клиентов, последний успешный снимок, ошибку/устаревание. При ошибке collector сохраняет прежние данные и время, включая перезапуск; свежесть 30 секунд считает сервер, история не сохраняется. MAC не связывается предположениями с UUID браузера. Существующий GetDevices расширен Wi-Fi-полем с теми же venue/role guards, нового endpoint/миграций/зависимостей нет. Обновлены protobuf/OpenAPI и оба generated clients.
- Проверки этапов 14–15: gofmt, `go test ./...`, race Cudy/wifi/presence/app, frontend type-check/lint/lint:css/build и self-check состояния прошли. Mock Cudy проверяет hash/CSRF/session/readonly GET, DOM/оба диапазона/сигнал/неизвестную разметку; снимок проверен атомарно и с управляемыми часами; CLI self-check подтвердил сохранение клиентов/времени после перезапуска при недоступном роутере. Firefox выявил прежнюю гонку раннего возврата ZIP до загрузки ID игры; действия передачи/возврата теперь появляются только после загрузки положительного ID. Итоговый изолированный Firefox-прогон обоих форматов (1440/390 px) прошёл: Wi-Fi fixtures, устройства/роли/polling и полный цикл ZIP до повторного возврата после перезапуска. Полный API-flow с PostgreSQL/race-регрессиями также прошёл; временные стенды удалены. `load.py` прошёл на отдельном Docker-стенде без WAN: 100 виртуальных профилей, 25 команд, 60 секунд, 300 heartbeat, 1200 story, 50 ходов, 25 ответов и 12 опросов состояния; ровно 100 доступных устройств и ноль HTTP-ошибок. p95: heartbeat 118 мс, story 68 мс, move 109 мс, answers 45 мс, devices 138 мс (бюджет теста 2 с). Нагрузка backend идёт по HTTP напрямую; DNS/HTTPS/Nginx проверены отдельно `dns.py --docker`/`https.py --docker`. Ручное ревью по стандартам и независимое read-only ревью выполнены; замечание о ложном пустом DOM исправлено и покрыто регрессией.
- Следующий шаг: аппаратная контрольная точка этапа 15 по `tests/venue/README.md`: после отдельно согласованного обновления площадки Android/iPhone, отключённые WAN/мобильные данные, 100 настоящих Wi-Fi-устройств и покрытие зала. Пользователь выбрал автоматическую нагрузку 100 виртуальных клиентов с отдельной ручной аппаратной приёмкой. До её подтверждения ED-029 остаётся `IN_PROGRESS` и не переносится в архив. Всего 15 этапов. ZIP остаётся неподписанным, фактический ZIP 1 ГиБ не проверен; развёртывание, commit/push не выполнялись.
### ED-024 — Общая дизайн-система фронтенда
+51 -1
View File
@@ -62,7 +62,8 @@ python3 tests/venue/flow.py --apply --browser
(`offline`). Проверяются клавиатурный focus, отказ на ход с сохранением ввода,
блокировка повторной отправки и кнопки на паузе при уже введённом коде.
Локальный HTTP-прокси переключает один публичный адрес между двумя реальными
backend, имитируя смену DNS; API-ответы не подменяются. Тестовые ссылки, ZIP,
backend, имитируя смену DNS; Игровые API-ответы не подменяются; ошибки, пустой список и состояния Wi-Fi
панели мониторинга дополнительно проверяются управляемыми ответами GetDevices. Тестовые ссылки, ZIP,
профиль Firefox и сборки остаются во временном каталоге до конца прогона.
`--results` дополнительно проверяет этап 9: серверный сервис собирает ZIP
@@ -155,3 +156,52 @@ Firefox проверяет игрока, организатора и экран
ошибку/восстановление и отбрасывание ответов после смены игры, ухода и unmount.
Временные границы connected остаются в тестах сервиса этапа 12 с управляемыми
часами; клиент пороги не пересчитывает.
## Wi-Fi и нагрузка (этапы 14–15)
```sh
python3 tests/venue/load.py
python3 tests/venue/flow.py --apply --browser
```
`load.py` использует тот же одноразовый стенд без WAN: 100 виртуальных UUID,
25 новых команд по четыре браузера, 60 секунд. Каждый профиль отправляет
heartbeat раз в 20 секунд и читает историю раз в 5 секунд; по одному профилю
команды выполняют два хода и сохраняют ответ. Организатор опрашивает состояние
раз в 5 секунд. Проверяются ровно 100 доступных устройств, 50 сохранённых ходов,
25 ответивших команд и отсутствие HTTP-ошибок. Отчёт содержит число запросов,
p50/p95/max; p95 ≤ 2 секунды — бюджет этого теста, не обещание для любого железа.
Запросы идут прямо к backend по HTTP: DNS, HTTPS и прокси проверяются отдельно
`dns.py --docker` и `https.py --docker`. Это нагрузка backend, не проверка радио.
Go-тесты адаптера Cudy проверяют вход/продление сессии, ограничение URL/ответа,
разбор обеих полос, отсутствующий сигнал, ошибку при неизвестной разметке,
атомарный снимок, свежесть с управляемыми часами и venue/prod/роли. Firefox
проверяет MAC/IP/диапазон/dBm, устаревание, ошибку и отсутствие сигнала; эти
состояния Wi-Fi задаются fixture. Отдельно адаптер проверен чтением настоящего
TR3000 V1 2.5.27-20260804-183625 без изменения настроек роутера.
### Аппаратная контрольная точка этапа 15
После согласованного обновления реальной площадки, только на тестовой игре:
1. Включить сбор Cudy по корневому README; отключить WAN и мобильные данные.
На Android/iPhone открыть прежние домены, проверить DNS и доверенный HTTPS.
2. Подключить 100 настоящих Wi-Fi-устройств (несколько вкладок одного профиля
не заменяют устройства); открыть игру 25 командами, держать нагрузку
минимум 60 секунд, выполнять ходы и сохранять ответы.
3. Сверить клиентов Cudy с панелью, диапазоны и доступный сигнал; пройти края
и проблемные зоны площадки, проверить материалы и действия в каждой зоне.
MAC/UUID не сопоставлять предположениями; «Нет связи» относится к heartbeat.
4. Проверить паузу/продолжение, перезапуск venue и повторную регистрацию
браузеров, смену контекста/несколько вкладок; убедиться, что панели в prod нет.
5. Временно остановить сборщик: через 30 секунд снимок должен устареть,
прежние данные и время остаться видимыми. Запустить снова и проверить
восстановление. Потерю API и восстановление проверить без потери снимка.
6. Завершить игру, вернуть ZIP на источник, проверить результаты и прежние
ссылки. Зафиксировать оборудование, число клиентов, длительность,
задержки/ошибки и покрытие; сообщить результат для приёмки ED-029.
Автоматические проверки не заменяют эту приёмку. Пока она не подтверждена,
ED-029 остаётся `IN_PROGRESS`. Фактический ZIP 1 ГиБ также не проверен.
+5 -1
View File
@@ -76,7 +76,7 @@ def check_browser_flow(stand, fixtures):
if js('return ' + script):
return
time.sleep(.1)
raise AssertionError('Browser timed out: ' + description)
raise AssertionError('Browser timed out: ' + description + ': ' + str(js('return document.body.innerText')).replace('\n', ' ')[:1500])
def navigate(path):
command('WebDriver:Navigate', {'url': stand.public + path})
@@ -177,6 +177,10 @@ def check_browser_flow(stand, fixtures):
wait("!document.body.innerText.includes('Не удалось обновить состояние')", 'state recovery')
js("window.stateMode='empty';return true;")
wait("document.body.innerText.includes('Устройства этой игры пока не зарегистрированы')", 'empty devices', 10)
js("window.fetch=(url,options)=>{if(String(url).includes('/api/devices?')){window.stateCalls++;return Promise.resolve(new Response(JSON.stringify({devices:[],serverTime:new Date().toISOString(),wifi:{enabled:true,hasError:window.wifiFailed,stale:window.wifiFailed,collectedAt:new Date().toISOString(),clients:[{MAC:'AA:BB:CC:DD:EE:01',IP:'192.168.10.9',band:'5 GHz',signal:window.wifiFailed?undefined:-65}]}}),{headers:{'Content-Type':'application/json'}}));}return window.stateFetch(url,options)};return true;")
wait("document.body.innerText.includes('-65 dBm')", 'Wi-Fi client and dBm', 10)
js("window.wifiFailed=true;return true;")
wait("document.body.innerText.includes('Данные устарели') && document.body.innerText.includes('Не удалось обновить данные Cudy') && document.body.innerText.includes('Нет данных')", 'stale Wi-Fi and missing signal', 10)
assert js("const tab=[...document.querySelectorAll('.n-tabs-tab[data-name]')].find(t=>t.textContent.trim()==='Игра');tab.click();return true;")
wait("location.search.includes('tab=game')", 'leave state tab')
calls = js('return window.stateCalls')
+9
View File
@@ -181,6 +181,15 @@ with tempfile.TemporaryDirectory(prefix="venue-https-") as directory:
assert {(p["protocol"], str(p["published"])) for p in dns["ports"]} == {("udp", "53"), ("tcp", "53")}
assert all(p["host_ip"] == "127.0.0.1" for p in dns["ports"])
assert any(v["target"] == "/etc/coredns/Corefile" and v["read_only"] for v in dns["volumes"])
collector = https["services"]["cudy_collector"]
backend = https["services"]["evening_detective_server"]
assert set(collector["networks"]) == {"entrance"} and not collector.get("ports")
assert set(backend["networks"]) == {"default"}
assert "DATABASE_URL" not in collector["environment"] and "S3_PASSWORD" not in collector["environment"]
assert any(v["target"] == "/run/secrets/cudy" and v["read_only"] for v in collector["volumes"])
assert any(v["target"] == "/run/cudy" and v["read_only"] for v in backend["volumes"])
assert not any(v["target"] == "/run/secrets/cudy" for v in backend["volumes"])
assert collector["environment"]["CUDY_SNAPSHOT_FILE"] == server["CUDY_SNAPSHOT_FILE"]
print("PASS: Compose merge, HTTPS addresses, ports, read-only mounts and preserved data volumes")
suffix = uuid.uuid4().hex[:10]
network = "venue-https-test-" + suffix
+98
View File
@@ -0,0 +1,98 @@
"""100 virtual clients in disposable prod/venue; does not test Wi-Fi radios."""
import json
import math
from concurrent.futures import ThreadPoolExecutor
import statistics
import sys
import tempfile
import threading
import time
import uuid
sys.dont_write_bytecode = True
from flow import API, Stand, export_package, fixture
def check_load(stand):
data = fixture(stand, 'online')
venue = stand.apis['venue']
venue.request('/api/games/import', export_package(stand.apis['prod'], data['game']))
game = data['game']
teams = [venue.request('/api/teams', {'gameID': game, 'name': f'TEST load {i}', 'participantsCount': 4}) for i in range(25)]
venue.request(f'/api/games/{game}/start', {}, 'PUT')
samples = {}
lock = threading.Lock()
barrier = threading.Barrier(101)
ids = [str(uuid.uuid4()) for _ in range(100)]
connections = [str(uuid.uuid4()) for _ in range(100)]
def measured(name, action):
before = time.perf_counter()
result = action()
with lock:
samples.setdefault(name, []).append(time.perf_counter() - before)
return result
def client(index):
public = API(venue.base)
team = teams[index // 4]
heartbeat = {'deviceID': ids[index], 'connectionID': connections[index], 'teamID': team['ID'], 'password': team['password']}
barrier.wait(timeout=30)
started = time.monotonic()
next_heartbeat = next_story = started
moved = False
while time.monotonic() - started < 60:
now = time.monotonic()
if now >= next_heartbeat:
result = measured('heartbeat', lambda: public.request('/api/devices/heartbeat', heartbeat))
assert result['enabled'] is True
next_heartbeat += 20
if not moved and index % 4 == 0:
for code in ('p1', 'p2'):
measured('move', lambda code=code: public.move(team, code))
measured('answers', lambda: public.request(f'/api/teams/{team["ID"]}/answers', {'password': team['password'], 'answers': [{'questionCode': 'q1', 'answer': 'Butler'}]}))
moved = True
if now >= next_story:
measured('story', lambda: public.story(team))
next_story += 5
time.sleep(.05)
with ThreadPoolExecutor(max_workers=100) as pool:
futures = [pool.submit(client, index) for index in range(100)]
barrier.wait(timeout=30)
deadline = time.monotonic() + 60
while time.monotonic() < deadline:
snapshot = measured('devices', lambda: venue.request(f'/api/devices?gameID={game}'))
assert len(snapshot['devices']) <= 100
time.sleep(5)
for future in futures:
future.result()
devices = venue.request(f'/api/devices?gameID={game}')['devices']
assert {device['ID'] for device in devices} == set(ids)
assert all(device['connected'] and len(device['connections']) == 1 for device in devices)
for team in teams:
assert len(API(venue.base).story(team)['actions']) == 2
answers = venue.request(f'/api/games/{game}/answers')['teams']
assert {row['team']['ID'] for row in answers if any(answer.get('answer') == 'Butler' for answer in row.get('answers', []))} == {team['ID'] for team in teams}
report = {}
for name, values in sorted(samples.items()):
values.sort()
p95 = values[math.ceil(.95 * len(values)) - 1]
report[name] = {'requests': len(values), 'p50_ms': round(statistics.median(values) * 1000), 'p95_ms': round(p95 * 1000), 'max_ms': round(values[-1] * 1000)}
assert p95 <= 2, f'{name} exceeds the test budget p95 <= 2s'
print('PASS: 100 profiles, 25 teams, 60s, 100 connected devices, 50 moves, 25 answers, zero HTTP errors', flush=True)
print(json.dumps(report, sort_keys=True), flush=True)
def main():
with tempfile.TemporaryDirectory(prefix='ed029-load-') as directory:
stand = Stand(directory)
try:
stand.start()
check_load(stand)
finally:
stand.close()
if __name__ == '__main__':
main()
+2
View File
@@ -4,3 +4,5 @@ frontend/dist/
migrations/
certificates/
packages/
/cudy/
+21 -1
View File
@@ -5,7 +5,7 @@ x-app: &app
build:
context: .
dockerfile: Dockerfile.backend
environment:
environment: &app-environment
APP_MODE: venue
DATABASE_URL: postgresql://${POSTGRES_USER:?}:${POSTGRES_PASSWORD:?}@postgres_evening_detective_server:5432/${POSTGRES_DB:?}?sslmode=disable
APP_BASE_URL: ${APP_BASE_URL:?}
@@ -64,6 +64,11 @@ services:
evening_detective_server:
<<: *app
environment:
<<: *app-environment
CUDY_SNAPSHOT_FILE: /run/cudy/state.json
volumes:
- cudy_state:/run/cudy:ro
depends_on:
migrate:
condition: service_completed_successfully
@@ -102,6 +107,20 @@ services:
condition: service_completed_successfully
restart: "no"
cudy_collector:
image: evening-detective-venue-app:1
command: [/usr/local/bin/evening_detective_server, --collect-cudy]
environment:
CUDY_URL: ${CUDY_URL:-http://192.168.10.1}
CUDY_PASSWORD_FILE: ${CUDY_PASSWORD_FILE:-}
CUDY_SNAPSHOT_FILE: /run/cudy/state.json
volumes:
- ./cudy:/run/secrets/cudy:ro
- cudy_state:/run/cudy
networks:
- entrance
restart: unless-stopped
evening_detective_frontend:
image: byjg/static-httpserver:latest
environment:
@@ -129,6 +148,7 @@ services:
restart: unless-stopped
volumes:
cudy_state:
# Preserve existing data from the former Compose project.
postgres_data:
name: evening-detective-local_postgres_data