up
This commit is contained in:
@@ -1,20 +1,30 @@
|
|||||||
.PHONY: check build prod-build publish
|
.PHONY: check build prod-build publish venue venue-update
|
||||||
|
|
||||||
|
PROD_DIR ?= prod
|
||||||
|
|
||||||
build:
|
build:
|
||||||
$(MAKE) -C evening_detective_server build-linux
|
$(MAKE) -C evening_detective_server build-linux
|
||||||
npm --prefix evening_detective_frontend run build
|
@test -n "$(PROD_DIR)"
|
||||||
|
@set -e; for dir in "$(PROD_DIR)" venue; do \
|
||||||
|
mkdir -p "$$dir/backend" "$$dir/frontend/dist"; \
|
||||||
|
rm -rf "$$dir/migrations"; \
|
||||||
|
mkdir -p "$$dir/migrations"; \
|
||||||
|
cp evening_detective_server/bin/evening_detective_server "$$dir/backend/evening_detective_server"; \
|
||||||
|
cp evening_detective_server/migrations/*.sql "$$dir/migrations/"; \
|
||||||
|
done
|
||||||
|
CGO_ENABLED=0 GOBIN="$(abspath venue/backend)" go install github.com/pressly/goose/v3/cmd/goose@v3.27.1
|
||||||
|
cp evening_detective_server/scripts/dev-user.sql venue/backend/dev-user.sql
|
||||||
|
npm --prefix evening_detective_frontend run type-check
|
||||||
|
npm --prefix evening_detective_frontend run build-only -- --outDir "$(abspath $(PROD_DIR)/frontend/dist)" --emptyOutDir
|
||||||
|
VITE_API_URL=/. npm --prefix evening_detective_frontend run build-only -- --outDir "$(abspath venue/frontend/dist)" --emptyOutDir
|
||||||
|
|
||||||
prod-build:
|
prod-build: build
|
||||||
rm -rf prod/backend prod/frontend/dist prod/migrations
|
|
||||||
mkdir -p prod/backend prod/frontend/dist prod/migrations
|
venue:
|
||||||
|
bash venue/deploy.sh
|
||||||
$(MAKE) -C evening_detective_server build-linux
|
|
||||||
cp evening_detective_server/bin/evening_detective_server prod/backend/evening_detective_server
|
venue-update:
|
||||||
|
bash venue/deploy.sh --build
|
||||||
npm --prefix evening_detective_frontend run build
|
|
||||||
cp -R evening_detective_frontend/dist/. prod/frontend/dist/
|
|
||||||
|
|
||||||
cp evening_detective_server/migrations/*.sql prod/migrations/
|
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
@test -n "$(MSG)" || (echo 'Usage: make publish MSG="commit message"' >&2; exit 1)
|
@test -n "$(MSG)" || (echo 'Usage: make publish MSG="commit message"' >&2; exit 1)
|
||||||
|
|||||||
@@ -61,6 +61,75 @@ make dev-user
|
|||||||
По умолчанию создаётся пользователь `dev@example.com` с паролем `dev123456`.
|
По умолчанию создаётся пользователь `dev@example.com` с паролем `dev123456`.
|
||||||
Для изменения используйте `DEV_USER_EMAIL` и `DEV_USER_PASSWORD`.
|
Для изменения используйте `DEV_USER_EMAIL` и `DEV_USER_PASSWORD`.
|
||||||
|
|
||||||
|
## Автономная площадка (ED-029, этапы 1–2)
|
||||||
|
|
||||||
|
`venue/` — окружение для проведения игры на площадке без интернета.
|
||||||
|
Локальная разработка описана выше и запускается отдельно.
|
||||||
|
|
||||||
|
Из корня workspace запустите:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make venue
|
||||||
|
```
|
||||||
|
|
||||||
|
Первый запуск с интернетом сам создаёт настройки и случайные секреты в
|
||||||
|
`venue/.env`, готовит образы, запускает миграции и создаёт организатора.
|
||||||
|
Повторные запуски используют готовое окружение без интернета.
|
||||||
|
Email и пароль для входа указаны в `VENUE_USER_EMAIL` и `VENUE_USER_PASSWORD`
|
||||||
|
в `venue/.env`; существующий файл настроек не перезаписывается.
|
||||||
|
Аккаунт организатора при запуске синхронизируется с этими значениями.
|
||||||
|
|
||||||
|
После изменения кода обновите площадку, пока интернет доступен:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make venue-update
|
||||||
|
```
|
||||||
|
|
||||||
|
Сайт доступен по `http://localhost:8088`; API и материалы обслуживаются тем же
|
||||||
|
прокси. Для подключения по LAN задайте `VENUE_BIND_IP=0.0.0.0` и
|
||||||
|
`APP_BASE_URL=http://<постоянный-IP-ноутбука>:8088` в `venue/.env`, затем
|
||||||
|
повторите `make venue`. Прежние домены и доверенный HTTPS
|
||||||
|
требуют отдельной настройки DNS и сертификатов на этапе 3.
|
||||||
|
|
||||||
|
Образы приложения, мигратора и прокси готовятся заранее. Runtime запускается
|
||||||
|
без сборки и скачивания; приложение, база и RustFS подключены только к
|
||||||
|
внутренней Docker-сети. Прокси дополнительно подключён к сети публикации порта.
|
||||||
|
База и RustFS сохраняются в именованных томах.
|
||||||
|
Миграции Goose выполняются до старта приложения. Для остановки без потери данных:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose -f venue/docker-compose.yml --env-file venue/.env down
|
||||||
|
```
|
||||||
|
|
||||||
|
Не используйте `down -v`: это удаляет данные. После изменений кода или миграций
|
||||||
|
выполните `make venue-update`, пока интернет доступен. Секреты, сборка,
|
||||||
|
сертификаты и пакеты игры игнорируются 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
|
||||||
|
docker compose -f venue/docker-compose.yml --env-file venue/.env down
|
||||||
|
docker compose -f venue/docker-compose.yml --env-file venue/.env up -d --no-build --pull never
|
||||||
|
python3 tests/venue/smoke.py --verify
|
||||||
|
```
|
||||||
|
|
||||||
|
Последняя команда проверяет ранее созданные записи и файл после перезапуска.
|
||||||
|
Тестовые данные остаются в локальной базе; повторное создание запускайте только
|
||||||
|
когда нужен новый набор. Дождитесь ответа `/api/test/ping` перед проверкой.
|
||||||
|
|
||||||
## Проверки перед изменениями
|
## Проверки перед изменениями
|
||||||
|
|
||||||
Сервер:
|
Сервер:
|
||||||
@@ -93,12 +162,26 @@ make publish MSG="Исправить подсчёт ходов в игре"
|
|||||||
```
|
```
|
||||||
|
|
||||||
Команда вызывает локальные build-команды репозиториев, создаёт коммит с
|
Команда вызывает локальные build-команды репозиториев, создаёт коммит с
|
||||||
указанным сообщением и выполняет `git push`. Для одной только сборки используйте:
|
указанным сообщением и выполняет `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
|
```sh
|
||||||
make build
|
make build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`make prod-build` выполняет ту же общую сборку. `make venue-update` вызывает её
|
||||||
|
и дополнительно подготавливает Docker-образы для автономного запуска. Команды
|
||||||
|
обновляют только генерируемые артефакты в `prod`; конфигурация `prod` не меняется.
|
||||||
|
|
||||||
## Окружение и секреты
|
## Окружение и секреты
|
||||||
|
|
||||||
Файлы `.env` предназначены только для локальных значений и не коммитятся.
|
Файлы `.env` предназначены только для локальных значений и не коммитятся.
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Проверки площадки для разработчика
|
||||||
|
|
||||||
|
Для работы площадки эти файлы не нужны. Обычный запуск: `make venue`.
|
||||||
|
|
||||||
|
- `deploy.py` — проверяет первый запуск, запуск без интернета и обновление без Docker.
|
||||||
|
- `smoke.py` — проверяет работающую площадку через API. Без `--verify` создаёт
|
||||||
|
тестовые данные; с `--verify` проверяет ранее созданные данные.
|
||||||
|
|
||||||
|
Команды из корня проекта:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tests/venue/deploy.py
|
||||||
|
python3 tests/venue/smoke.py --verify
|
||||||
|
```
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""Check first, offline and update runs without Docker or production writes."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
source = Path(__file__).resolve().parents[2] / "venue" / "deploy.sh"
|
||||||
|
for name, cold, update in [("first", True, False), ("offline", False, False), ("update", False, True)]:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
venue = root / "venue"
|
||||||
|
venue.mkdir()
|
||||||
|
shutil.copy(source, venue / "deploy.sh")
|
||||||
|
log = root / "commands.log"
|
||||||
|
bin_dir = root / "bin"
|
||||||
|
bin_dir.mkdir()
|
||||||
|
for command in ["docker", "make", "npm"]:
|
||||||
|
stub = bin_dir / command
|
||||||
|
stub.write_text(
|
||||||
|
'#!/bin/sh\n'
|
||||||
|
'echo "$(basename "$0") $*" >> "$TEST_LOG"\n'
|
||||||
|
'if [ "$1 $2" = "image inspect" ] && [ "$TEST_COLD" = 1 ]; then exit 1; fi\n'
|
||||||
|
)
|
||||||
|
stub.chmod(0o755)
|
||||||
|
if not cold:
|
||||||
|
(venue / "migrations").mkdir()
|
||||||
|
(venue / ".env").write_text("EXISTING_SECRET=unchanged\n")
|
||||||
|
for artifact in ["backend/evening_detective_server", "backend/goose", "backend/dev-user.sql", "frontend/dist/index.html"]:
|
||||||
|
path = venue / artifact
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.touch()
|
||||||
|
env = {**os.environ, "PATH": str(bin_dir) + os.pathsep + os.environ["PATH"], "TEST_LOG": str(log), "TEST_COLD": str(int(cold))}
|
||||||
|
subprocess.run(["bash", str(venue / "deploy.sh"), *(["--build"] if update else [])], env=env, check=True, capture_output=True)
|
||||||
|
commands = log.read_text()
|
||||||
|
assert "volume create evening-detective-local_postgres_data" in commands, name
|
||||||
|
assert "volume create evening-detective-local_rustfs_data" in commands, name
|
||||||
|
assert "compose up -d --no-build --pull never" in commands, name
|
||||||
|
assert "compose --profile setup run --rm bootstrap" in commands, name
|
||||||
|
assert ("make -C .. build" in commands) == (cold or update), name
|
||||||
|
assert ("compose pull" in commands) == (cold or update), name
|
||||||
|
assert ("compose build --pull=false" in commands) == (cold or update), name
|
||||||
|
if cold:
|
||||||
|
config = dict(line.split("=", 1) for line in (venue / ".env").read_text().splitlines())
|
||||||
|
assert len(config["JWT_SECRET"]) == 64
|
||||||
|
assert len(config["VENUE_USER_PASSWORD"]) == 16
|
||||||
|
assert (venue / ".env").stat().st_mode & 0o777 == 0o600
|
||||||
|
else:
|
||||||
|
assert (venue / ".env").read_text() == "EXISTING_SECRET=unchanged\n", name
|
||||||
|
print("PASS: automatic first setup, offline reuse, explicit update and preserved secrets")
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"""Create a venue test game, then verify persistence with --verify after restart."""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
root = Path(__file__).resolve().parents[2] / "venue"
|
||||||
|
assert (root / "backend" / "evening_detective_server").is_file(), "Backend artifact missing"
|
||||||
|
assert (root / "backend" / "goose").is_file(), "Migration tool missing"
|
||||||
|
assert (root / "backend" / "dev-user.sql").is_file(), "Account bootstrap SQL missing"
|
||||||
|
assert (root / "frontend" / "dist" / "index.html").is_file(), "Frontend artifact missing"
|
||||||
|
assert list((root / "migrations").glob("*.sql")), "Migrations missing"
|
||||||
|
config = dict(
|
||||||
|
line.split("=", 1)
|
||||||
|
for line in (root / ".env").read_text().splitlines()
|
||||||
|
if line and not line.startswith("#")
|
||||||
|
)
|
||||||
|
bind = config.get("VENUE_BIND_IP", "127.0.0.1")
|
||||||
|
if bind == "0.0.0.0":
|
||||||
|
bind = "127.0.0.1"
|
||||||
|
base = f'http://{bind}:{config.get("VENUE_PORT", "8088")}'
|
||||||
|
token = ""
|
||||||
|
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||||
|
|
||||||
|
|
||||||
|
def request(path, body=None, raw=False):
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if token:
|
||||||
|
headers["Authorization"] = "Bearer " + token
|
||||||
|
req = urllib.request.Request(
|
||||||
|
base + path,
|
||||||
|
data=None if body is None else json.dumps(body).encode(),
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
with opener.open(req, timeout=10) as response:
|
||||||
|
data = response.read()
|
||||||
|
return data if raw else json.loads(data)
|
||||||
|
|
||||||
|
|
||||||
|
def verify(state):
|
||||||
|
game = request(f'/api/games/{state["gameID"]}')["game"]
|
||||||
|
teams = game["teams"]
|
||||||
|
assert any(
|
||||||
|
team["ID"] == state["teamID"]
|
||||||
|
and team["participationStatus"] == "confirmed"
|
||||||
|
for team in teams
|
||||||
|
), "Test team or confirmed participation was not preserved"
|
||||||
|
assert request("/api/files/" + state["filename"], raw=True) == base64.b64decode(
|
||||||
|
state["data"]
|
||||||
|
), "Uploaded material was not preserved"
|
||||||
|
assert b'<div id="app">' in request("/games", raw=True), "SPA route failed"
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
token = request(
|
||||||
|
"/api/auth/login",
|
||||||
|
{
|
||||||
|
"email": config["VENUE_USER_EMAIL"],
|
||||||
|
"password": config["VENUE_USER_PASSWORD"],
|
||||||
|
},
|
||||||
|
)["accessToken"]
|
||||||
|
state_file = root / "packages" / "smoke.json"
|
||||||
|
if "--verify" in sys.argv:
|
||||||
|
state = json.loads(state_file.read_text())
|
||||||
|
else:
|
||||||
|
scenario = request("/api/scenario", {"name": "TEST venue smoke"})["ID"]
|
||||||
|
start = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=2)
|
||||||
|
game = request(
|
||||||
|
"/api/games",
|
||||||
|
{
|
||||||
|
"name": "TEST venue smoke",
|
||||||
|
"description": "Disposable offline environment check",
|
||||||
|
"startAt": start.isoformat(),
|
||||||
|
"scenarioID": scenario,
|
||||||
|
"format": "online",
|
||||||
|
},
|
||||||
|
)["ID"]
|
||||||
|
team = request(
|
||||||
|
"/api/teams",
|
||||||
|
{"gameID": game, "name": "TEST team", "participantsCount": 2},
|
||||||
|
)["ID"]
|
||||||
|
data = base64.b64encode(b"%PDF-1.4\n%venue-smoke\n%%EOF\n").decode()
|
||||||
|
filename = request(
|
||||||
|
"/api/files/upload", {"filename": "venue-smoke.pdf", "data": data}
|
||||||
|
)["filename"]
|
||||||
|
state = {"gameID": game, "teamID": team, "filename": filename, "data": data}
|
||||||
|
state_file.parent.mkdir(exist_ok=True)
|
||||||
|
state_file.write_text(json.dumps(state))
|
||||||
|
verify(state)
|
||||||
|
for path, body in [
|
||||||
|
("/api/auth/signup", {"username": "Test", "email": "test@venue.invalid", "acceptTerms": True, "acceptPrivacy": True}),
|
||||||
|
("/api/auth/refresh-password", {"email": config["VENUE_USER_EMAIL"]}),
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
request(path, body)
|
||||||
|
except urllib.error.HTTPError as error:
|
||||||
|
assert error.code == 400, "Unexpected disabled operation status"
|
||||||
|
assert json.loads(error.read())["code"] == 9, "Expected FailedPrecondition"
|
||||||
|
else:
|
||||||
|
raise AssertionError("Email-based account operation succeeded locally")
|
||||||
|
print("PASS: login, test game, confirmed team, material, SPA and disabled email operations")
|
||||||
|
except urllib.error.HTTPError as error:
|
||||||
|
# Do not print response bodies: authentication responses may contain credentials.
|
||||||
|
print(f"FAIL: venue API returned HTTP {error.code}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
*
|
||||||
|
!Dockerfile.backend
|
||||||
|
!backend/
|
||||||
|
!backend/evening_detective_server
|
||||||
|
!backend/goose
|
||||||
|
!migrations/
|
||||||
|
!migrations/*.sql
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.env
|
||||||
|
backend/
|
||||||
|
frontend/dist/
|
||||||
|
migrations/
|
||||||
|
certificates/
|
||||||
|
packages/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
FROM alpine:3.22
|
||||||
|
COPY backend/evening_detective_server /usr/local/bin/evening_detective_server
|
||||||
|
COPY backend/goose /usr/local/bin/goose
|
||||||
|
COPY migrations /migrations
|
||||||
|
CMD ["/usr/local/bin/evening_detective_server"]
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
||||||
|
if [[ ! -f .env ]]; then
|
||||||
|
command -v openssl >/dev/null
|
||||||
|
(
|
||||||
|
umask 077
|
||||||
|
cat > .env <<EOF
|
||||||
|
APP_BASE_URL=http://localhost:8088
|
||||||
|
VENUE_BIND_IP=127.0.0.1
|
||||||
|
VENUE_PORT=8088
|
||||||
|
POSTGRES_USER=evening_detective
|
||||||
|
POSTGRES_DB=evening_detective
|
||||||
|
POSTGRES_PASSWORD=$(openssl rand -hex 32)
|
||||||
|
JWT_SECRET=$(openssl rand -hex 32)
|
||||||
|
PARTICIPATION_SECRET=$(openssl rand -hex 32)
|
||||||
|
S3_USER=evening_detective
|
||||||
|
S3_PASSWORD=$(openssl rand -hex 32)
|
||||||
|
S3_BUCKET=evening-detective
|
||||||
|
GAME_TIMEZONE=Asia/Barnaul
|
||||||
|
VENUE_USER_USERNAME=Организатор
|
||||||
|
VENUE_USER_EMAIL=organizer@venue.invalid
|
||||||
|
VENUE_USER_PASSWORD=$(openssl rand -hex 8)
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
prepare=false
|
||||||
|
case "${1:-}" in
|
||||||
|
--build) prepare=true ;;
|
||||||
|
'') ;;
|
||||||
|
*) echo 'Используйте make venue или make venue-update' >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
for image in evening-detective-venue-app:1 postgres:15 rustfs/rustfs:latest byjg/static-httpserver:latest nginx:alpine; do
|
||||||
|
if ! docker image inspect "$image" >/dev/null 2>&1; then prepare=true; fi
|
||||||
|
done
|
||||||
|
for artifact in backend/evening_detective_server backend/goose backend/dev-user.sql frontend/dist/index.html; do
|
||||||
|
if [[ ! -f "$artifact" ]]; then prepare=true; fi
|
||||||
|
done
|
||||||
|
if [[ ! -d migrations ]]; then prepare=true; fi
|
||||||
|
if [[ "$prepare" == true ]]; then
|
||||||
|
echo 'Подготовка площадки: потребуется интернет.'
|
||||||
|
if ! docker image inspect my-go-builder >/dev/null 2>&1; then
|
||||||
|
make -C ../evening_detective_server build-builder
|
||||||
|
fi
|
||||||
|
if [[ ! -d ../evening_detective_frontend/node_modules ]]; then
|
||||||
|
npm --prefix ../evening_detective_frontend ci
|
||||||
|
fi
|
||||||
|
make -C .. build
|
||||||
|
docker compose pull postgres_evening_detective_server rustfs_evening_detective_server evening_detective_frontend proxy
|
||||||
|
docker pull alpine:3.22
|
||||||
|
docker compose build --pull=false
|
||||||
|
fi
|
||||||
|
for volume in evening-detective-local_postgres_data evening-detective-local_rustfs_data; do
|
||||||
|
docker volume create "$volume" >/dev/null
|
||||||
|
done
|
||||||
|
docker compose up -d --no-build --pull never
|
||||||
|
docker compose --profile setup run --rm bootstrap
|
||||||
|
docker compose ps
|
||||||
|
echo 'Площадка запущена. Адрес и данные входа находятся в venue/.env.'
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
name: evening-detective-venue
|
||||||
|
|
||||||
|
x-app: &app
|
||||||
|
image: evening-detective-venue-app:1
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.backend
|
||||||
|
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:?}
|
||||||
|
FILE_PREFIX_DOMAIN: ${APP_BASE_URL:?}/api/files/
|
||||||
|
GAME_TIMEZONE: ${GAME_TIMEZONE:-Asia/Barnaul}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:?}
|
||||||
|
PARTICIPATION_SECRET: ${PARTICIPATION_SECRET:?}
|
||||||
|
S3_HOST: http://rustfs_evening_detective_server:9000
|
||||||
|
S3_USER: ${S3_USER:?}
|
||||||
|
S3_PASSWORD: ${S3_PASSWORD:?}
|
||||||
|
S3_BUCKET: ${S3_BUCKET:-evening-detective}
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres_evening_detective_server:
|
||||||
|
image: postgres:15
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:?}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:?}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?}
|
||||||
|
TZ: ${GAME_TIMEZONE:-Asia/Barnaul}
|
||||||
|
PGTZ: ${GAME_TIMEZONE:-Asia/Barnaul}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: [CMD-SHELL, 'pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}" -h localhost']
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 12
|
||||||
|
|
||||||
|
rustfs_evening_detective_server:
|
||||||
|
image: rustfs/rustfs:latest
|
||||||
|
environment:
|
||||||
|
RUSTFS_ACCESS_KEY: ${S3_USER:?}
|
||||||
|
RUSTFS_SECRET_KEY: ${S3_PASSWORD:?}
|
||||||
|
RUSTFS_CONSOLE_ENABLE: "false"
|
||||||
|
RUSTFS_ADDRESS: 0.0.0.0:9000
|
||||||
|
volumes:
|
||||||
|
- rustfs_data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: [CMD-SHELL, 'curl -f http://localhost:9000/health']
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 12
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
<<: *app
|
||||||
|
entrypoint: [/bin/sh, -ec]
|
||||||
|
command: ['exec goose -dir /migrations postgres "$$DATABASE_URL" up']
|
||||||
|
depends_on:
|
||||||
|
postgres_evening_detective_server:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
evening_detective_server:
|
||||||
|
<<: *app
|
||||||
|
depends_on:
|
||||||
|
migrate:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
rustfs_evening_detective_server:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: [CMD, wget, -q, -T, '2', -O, /dev/null, 'http://127.0.0.1:8090/api/test/ping']
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 12
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
image: postgres:15
|
||||||
|
profiles: [setup]
|
||||||
|
environment:
|
||||||
|
PGHOST: postgres_evening_detective_server
|
||||||
|
PGUSER: ${POSTGRES_USER:?}
|
||||||
|
PGPASSWORD: ${POSTGRES_PASSWORD:?}
|
||||||
|
PGDATABASE: ${POSTGRES_DB:?}
|
||||||
|
VENUE_USER_USERNAME: ${VENUE_USER_USERNAME:-Организатор}
|
||||||
|
VENUE_USER_EMAIL: ${VENUE_USER_EMAIL:-organizer@venue.invalid}
|
||||||
|
VENUE_USER_PASSWORD: ${VENUE_USER_PASSWORD:?}
|
||||||
|
volumes:
|
||||||
|
- ./backend/dev-user.sql:/bootstrap.sql:ro
|
||||||
|
entrypoint: [/bin/sh, -ec]
|
||||||
|
command:
|
||||||
|
- >-
|
||||||
|
exec psql -v ON_ERROR_STOP=1
|
||||||
|
-v dev_username="$$VENUE_USER_USERNAME"
|
||||||
|
-v dev_email="$$VENUE_USER_EMAIL"
|
||||||
|
-v dev_password="$$VENUE_USER_PASSWORD"
|
||||||
|
-f /bootstrap.sql
|
||||||
|
depends_on:
|
||||||
|
migrate:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
evening_detective_frontend:
|
||||||
|
image: byjg/static-httpserver:latest
|
||||||
|
environment:
|
||||||
|
SPA_MODE: "true"
|
||||||
|
volumes:
|
||||||
|
- ./frontend/dist:/static:ro
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
image: nginx:alpine
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
ports:
|
||||||
|
- '${VENUE_BIND_IP:-0.0.0.0}:${VENUE_PORT:-8088}:80'
|
||||||
|
depends_on:
|
||||||
|
evening_detective_server:
|
||||||
|
condition: service_healthy
|
||||||
|
evening_detective_frontend:
|
||||||
|
condition: service_started
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- entrance
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# Preserve existing data from the former Compose project.
|
||||||
|
postgres_data:
|
||||||
|
name: evening-detective-local_postgres_data
|
||||||
|
external: true
|
||||||
|
rustfs_data:
|
||||||
|
name: evening-detective-local_rustfs_data
|
||||||
|
external: true
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
internal: true
|
||||||
|
entrance:
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
client_max_body_size 257m;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://evening_detective_server:8090;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://evening_detective_frontend:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user