Files
x0gp 978d36c505 feat(server): initial implementation
This commit captures the full server code accumulated across
several development sessions. It includes the following layers,
applied in roughly this order during development:

1. Base infrastructure
   - cmd/poc-server HTTP+WebSocket server, /health, /stats
   - internal/transport wire types (Envelope + all message types)
   - internal/catalog, internal/realtime, internal/stats
   - internal/storage/postgres with migrations 001-004
   - .env, .env.example, docker-compose, Dockerfile, scripts/

2. Swagger documentation
   - go get github.com/swaggo/http-swagger
   - swag init produces docs/docs.go
   - /swagger/index.html UI, /swagger/doc.json spec
   - annotations on health/stats/ws and catalog handlers

3. Races API
   - internal/races/{store,service,keyset,types}.go
   - migration 005_races.sql: finished_races, race_plans, race_queue
   - GET /api/races with keyset pagination, status filter
   - GET /api/races/upcoming
   - POST /api/races/queue/join, GET, DELETE
   - POST/GET /api/races/plans, DELETE /{id}
   - lobby.RaceMeta simplification (no host_id/host_name)

4. Races seeder
   - internal/races/seed with deterministic generator
   - --seed-races / --reset CLI flags in main.go
   - 30 finished + 5 live + 5 plans + 4 queue entries

5. Drivers and clans
   - internal/drivers, internal/clans (CRUD, validation)
   - migration 008_drivers_clans.sql
   - /api/clans and /api/drivers endpoints
   - 3-letter uppercase nickname/tag validation
   - lobby.DriverMeta: Nickname, AvatarURL, ClanID, ClanTag

6. Podium
   - internal/transport.RacePodiumEntry
   - lobby.SetDriverProfile for in-memory metadata sync
   - migration 007_podium.sql (podium JSONB column)
   - seeder populates top-3 per finished race

7. Live races persistence
   - migration 009_live_persistence.sql: live_races,
     live_race_drivers, lobby_drivers
   - internal/races/live_store.go: LiveStore with write-side
     mirror for lobby.Service mutations
   - Service.collectLive and Upcoming read from Postgres
   - main.go RestoreFromDB rehydrates lobby on startup

8. Unify live + finished into one races table
   - migration 010_unify_races.sql: rename finished_races to
     races, expand status CHECK, merge live rows
   - PgStore now hosts both write paths; LiveStore is a
     thin facade implementing lobby.Persistence
   - seeder resetAll drops only finished/cancelled rows and
     race_plans / race_queue / lobby_drivers

Each layer is consistent on its own; cross-layer changes are
visible in the file history. A future refactor may split this
commit into the per-stage boundaries listed above.
2026-06-22 22:01:09 +04:00

210 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# x0gp server (PoC)
Минимальный Go-сервер для сквозной проверки идеи x0gp: WebSocket-клиент
(браузер) отправляет команды руля/газа, сервер гоняет упрощённую
authoritative-физику на 60 Hz и шлёт снапшоты обратно.
**Это PoC.** Без биллинга, без CV, без агента на машинке. WebSocket +
race engine + Postgres (каталог tracks / cars) + Redis pub/sub.
---
## Архитектура PoC
```
browser -- WS(JSON) --> poc-server -- in-process --> RaceEngine (60 Hz tick)
\-> Snapshot fan-out via Hub
```
См. [ARCHITECTURE.md](../ARCHITECTURE.md) и [PLAN.md](./PLAN.md) для
общей картины и будущих фаз.
---
## Структура
```
server/
cmd/poc-server/main.go # точка входа, HTTP + WS, race engine tick
internal/
config/ # env-конфиг
transport/ # JSON-codec (PoC), envelope, типы сообщений
control/ # authoritative race engine (tick 60 Hz, физика)
realtime/ # WebSocket hub, fan-out, backpressure
catalog/ # tracks + cars, интерфейс Store + in-memory cache
seeddefs/ # Go-определения seed-данных (общие с SQL seed)
storage/postgres/ # pgx pool, embedded миграции, PgStore
migrations/ # 001_init.sql, 002_seed.sql
scripts/genseed/ # утилита для перегенерации 002_seed.sql
proto/ # формальная спецификация (proto3)
client_server.proto
server_agent.proto
buf.yaml, buf.gen.yaml
web/ # минимальный vanilla-JS клиент
index.html, main.js, style.css
Dockerfile # multi-stage, linux/arm64 для Orange Pi 5 Pro
docker-compose.yml # dev stack: postgres+timescale, redis, prom, grafana
Makefile # build, run, proto, test, docker, compose
.gitignore
go.mod
```
---
## Быстрый старт (на Orange Pi 5 Pro)
### 1. Подготовка
```bash
# Установить Go 1.26+ (если ещё не стоит).
wget https://go.dev/dl/go1.26.4.linux-arm64.tar.gz
sudo tar -C /usr/local -xzf go1.26.4.linux-arm64.tar.gz
export PATH=$PATH:/usr/local/go/bin
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
go version # должно быть go1.26.x
```
### 2. Клонирование и сборка
```bash
git clone https://github.com/<you>/x0gp.git
cd x0gp/server
# Тянем зависимости и собираем бинарь под arm64.
make tidy
make build # -> bin/poc-server-linux-arm64
```
### 3. Запуск (минимальный)
```bash
X0GP_DEV_MODE=true ./bin/poc-server-linux-arm64
```
Открыть в браузере на той же машине: `http://localhost:8080/`.
Подключиться через web-интерфейс — увидишь виртуальные машинки, которые
гоняют по сцене, реагируя на команды.
### 4. Запуск полного dev-стека (через docker compose)
```bash
make compose-up # поднимает postgres, redis, prometheus, grafana, api-gateway
# api-gateway будет на http://localhost:8080
# grafana: http://localhost:3000 (admin/admin)
make compose-down # остановить
```
### 5. Деплой в Coolify
(см. [../ops/HOME_SERVER.md](../ops/HOME_SERVER.md))
- Push репо на GitHub/Gitea.
- В Coolify: New Resource → Application → подключить репо.
- Build Pack: Dockerfile.
- Port: 8080.
- Env vars: `X0GP_DEV_MODE=true` для PoC.
- Healthcheck path: `/health`.
---
## Конфигурация (env vars)
| Variable | Default | Описание |
|---|---|---|
| `X0GP_HTTP_ADDR` | `:8080` | Адрес HTTP-сервера |
| `X0GP_TICK_RATE` | `60` | Race engine tick (Гц) |
| `X0GP_SNAPSHOT_RATE` | `30` | Snapshot fan-out (Гц) |
| `X0GP_MAX_CLIENTS` | `100` | Лимит одновременных WS |
| `X0GP_LOG_LEVEL` | `info` | `debug`/`info`/`warn`/`error` |
| `X0GP_DEV_MODE` | `true` | PoC-режим: без auth, несколько клиентов |
| `DATABASE_URL` | — | **обязательно**. Строка подключения к Postgres (например `postgres://x0gp:x0gp@localhost:5432/x0gp?sslmode=disable`). Сервер не стартует без неё. |
---
## WebSocket протокол
Все кадры — JSON-объекты с полями `type`, `seq`, `ts_ms`, `payload`.
См. [proto/client_server.proto](./proto/client_server.proto) — формальная
спецификация для будущего бинарного protobuf-формата (MVP/prod).
### Пример: подключиться и поуправлять машинкой (из консоли)
```bash
# Через websocat (apt install websocat).
websocat ws://localhost:8080/ws
# Ввести:
{"type":"client_hello","payload":{"version":"0.1"}}
{"type":"join_race","payload":{"car_slot":0,"car_name":"alice"}}
{"type":"input_state","payload":{"steering":0.5,"throttle":0.8,"brake":0}}
# ... крутить рулём и газом
```
Сервер сразу же начнёт присылать `race_snapshot` (30 Гц) с позициями
всех машинок.
---
## Метрики и логи
- `/health` — healthcheck (JSON).
- `/stats` — текущие connection count, snapshot drops, race phase.
- `/metrics` — Prometheus metrics (будет в S1).
- Логи: JSON в stdout (slog), удобно для Loki/Promtail.
---
## Хранилище (Postgres + TimescaleDB)
Каталог (tracks / cars) хранится в Postgres. Миграции лежат в
`internal/storage/postgres/migrations/*.sql`, встроены через `//go:embed`
и применяются автоматически при старте сервера (см.
`internal/storage/postgres/postgres.go:Migrate`). Уже применённые
миграции отслеживаются в таблице `schema_migrations`.
- `001_init.sql` — схема: `tracks`, `track_waypoints`, `track_tags`, `cars`,
плюс `CREATE EXTENSION timescaledb` (для будущего телеметрического
hypertable в S2).
- `002_seed.sql` — system-данные: 3 трассы (`oval-classic`,
`figure-eight`, `sprint-straight`) и 2 машины (`compact-1-27-touring`,
`compact-1-27-formula`). Сидовые Go-определения лежат в
`internal/catalog/seeddefs`; чтобы перегенерировать SQL, см.
`scripts/genseed`.
Локальный стек поднимается через `make compose-up` (см.
`docker-compose.yml`). Сервер стартует **fail-fast**: если
`DATABASE_URL` не задан или БД недоступна, процесс завершается с
ошибкой и кодом 1 — без БД каталог работать не будет.
## Команды разработки
```bash
make tidy # go mod tidy
make build # сборка
make run # локальный запуск (dev)
make test # go test ./...
make lint # go vet + golangci-lint
make proto # регенерация proto/gen/ (через Docker buf)
make docker # собрать api-gateway image
make compose-up # поднять dev стек
make compose-down # опустить dev стек
make clean # rm -rf bin/
```
---
## Что дальше
См. [PLAN.md](./PLAN.md) и [../ARCHITECTURE.md](../ARCHITECTURE.md):
- **S1 (MVP)**: добавить Postgres, Redis, JWT-auth, лобби.
- **S2**: overhead-CV на NPU Orange Pi, телеметрия в TimescaleDB.
- **S3**: биллинг через ЮKassa.
- **S4**: турниры.
---
## Лицензия
TBD (предлагается Apache 2.0).