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:
|
||||
$(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:
|
||||
rm -rf prod/backend prod/frontend/dist prod/migrations
|
||||
mkdir -p prod/backend prod/frontend/dist prod/migrations
|
||||
prod-build: build
|
||||
|
||||
$(MAKE) -C evening_detective_server build-linux
|
||||
cp evening_detective_server/bin/evening_detective_server prod/backend/evening_detective_server
|
||||
venue:
|
||||
bash venue/deploy.sh
|
||||
|
||||
npm --prefix evening_detective_frontend run build
|
||||
cp -R evening_detective_frontend/dist/. prod/frontend/dist/
|
||||
|
||||
cp evening_detective_server/migrations/*.sql prod/migrations/
|
||||
venue-update:
|
||||
bash venue/deploy.sh --build
|
||||
|
||||
publish:
|
||||
@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_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-команды репозиториев, создаёт коммит с
|
||||
указанным сообщением и выполняет `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
|
||||
make build
|
||||
```
|
||||
|
||||
`make prod-build` выполняет ту же общую сборку. `make venue-update` вызывает её
|
||||
и дополнительно подготавливает Docker-образы для автономного запуска. Команды
|
||||
обновляют только генерируемые артефакты в `prod`; конфигурация `prod` не меняется.
|
||||
|
||||
## Окружение и секреты
|
||||
|
||||
Файлы `.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