mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-16 20:47:55 +00:00
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.
210 lines
8.0 KiB
Markdown
210 lines
8.0 KiB
Markdown
# 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).
|