Files
MrixsCraft/docs/server/Specification.md
2026-06-09 18:14:59 +03:00

535 lines
20 KiB
Markdown
Raw 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.
# Спецификация серверной части Minecraft проекта (Backend & Infrastructure)
## 1. Стек технологий
* **Бэкенд-платформа:** Go (чистый net/http со стандартным роутером, для максимальной прозрачности и контроля, без внешних зависимостей).
* **База данных:** PostgreSQL (реляционная СУБД для надежного хранения транзакций токенов, пользователей и связей файлов).
* **Фронтенд (Сайт и Админка):** SPA на Vue.js / React / Svelte, либо классический монолит на Go-шаблонах (html/template) для максимального упрощения деплоя (всё в одном исполняемом файле).
* **Reverse Proxy и статика:** Caddy (автоматический HTTPS, отдача файлов, reverse proxy к Go-бэкенду — всё в одном бинарнике).
* **Контейнеризация:** Docker + Docker Compose.
* **Registry:** Gitea Container Registry.
* **Авто-обновление контейнеров:** Watchtower (pull новых образов из registry).
---
## 2. Архитектура хранилища файлов (Content-Addressable Storage - CAS)
Для экономии места на диске сервера и клиента, а также для сквозного кэширования, все файлы модов, библиотек и ассетов хранятся по их SHA-1 хэшам.
* **Путь на сервере:** `/var/www/cdn/files/[первые два символа хэша]/[полный хэш]`
* *Пример:* Файл с хэшем `a1b2c3d4e5...` будет лежать по пути `/var/www/cdn/files/a1/b2c3d4e5...` (это предотвращает замедление файловой системы при наличии десятков тысяч файлов в одной директории).
* **URL для скачивания:** `https://cdn.myserver.com/files/a1b2c3d4e5...`
---
## 3. Схема Базы Данных (PostgreSQL)
```sql
-- Таблица пользователей
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(32) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
uuid UUID UNIQUE NOT NULL,
role VARCHAR(16) DEFAULT 'user', -- 'user', 'admin'
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица скинов и плащей (ссылки на файлы в CAS)
CREATE TABLE player_textures (
user_id INT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
skin_hash VARCHAR(40), -- SHA-1 хэш файла скина в CAS
cape_hash VARCHAR(40), -- SHA-1 хэш файла плаща в CAS
is_slim BOOLEAN DEFAULT FALSE -- модель скина (Alex/Steve)
);
-- Сессии Yggdrasil (для авторизации лаунчера и игры)
CREATE TABLE yggdrasil_sessions (
client_token UUID NOT NULL,
access_token UUID PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
expires_at TIMESTAMP NOT NULL
);
-- Таблица модпаков (игровых серверов)
CREATE TABLE modpacks (
id SERIAL PRIMARY KEY,
slug VARCHAR(32) UNIQUE NOT NULL, -- например: 'hitech', 'magic'
name VARCHAR(64) NOT NULL,
minecraft_version VARCHAR(16) NOT NULL,
java_version INT NOT NULL, -- 8, 17, 21
server_ip VARCHAR(255) NOT NULL, -- IP:Port для добавления в servers.dat
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Единый реестр уникальных файлов (CAS)
CREATE TABLE global_files (
sha1 VARCHAR(40) PRIMARY KEY,
size_bytes BIGINT NOT NULL,
file_name VARCHAR(255) NOT NULL -- оригинальное имя файла (для истории)
);
-- Релизы лаунчера (заполняются через CI/CD)
CREATE TABLE launcher_releases (
id SERIAL PRIMARY KEY,
version VARCHAR(32) NOT NULL, -- например: '1.2.0'
os VARCHAR(16) NOT NULL, -- 'windows', 'linux', 'darwin'
arch VARCHAR(16) NOT NULL, -- 'amd64', 'arm64'
sha256 VARCHAR(64) NOT NULL, -- SHA-256 для проверки бинарника лаунчера
file_path VARCHAR(255) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
is_mandatory BOOLEAN DEFAULT TRUE, -- принудительное обновление
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
---
## 4. Спецификация API Эндпоинтов
### 4.1. Автоматический деплой из CI/CD (Protected Route)
Эндпоинт, который вызывает твой пайплайн сборки после успешной компиляции лаунчера.
* **Маршрут:** `POST /api/admin/launcher/release`
* **Авторизация:** Заголовок `X-CI-Token: <секретный_токен_из_секретов_репозитория>`
* **Тело запроса (Multipart Form Data):**
* `version`: "1.2.0"
* `os`: "windows"
* `arch`: "amd64"
* `sha256`: "hash_of_binary..."
* `file`: (бинарный файл лаунчера)
### 4.2. Yggdrasil API (Mojang Emulation)
* `POST /authserver/authenticate` — Аутентификация по логину/пароля. Возвращает UUID и токены.
* `POST /authserver/refresh` — Обновление сессии лаунчера по `clientToken` и `accessToken`.
* `POST /authserver/validate` — Быстрая проверка токена при запуске лаунчера.
* `GET /sessionserver/session/minecraft/profile/{uuid}` — Эндпоинт, к которому обращается сам игровой клиент для загрузки скинов игроков на сервере.
### 4.3. Публичное API сайта и лаунчера
* `POST /api/web/register` — Регистрация игрока.
* `POST /api/web/login` — Логин в личный кабинет сайта.
* `POST /api/web/profile/skin` — Загрузка скина (принимает PNG, считает его SHA-1, сохраняет в `/files/` и привязывает к юзеру).
* `GET /api/launcher/latest` — Возвращает информацию о последней версии лаунчера и ссылки на скачивание для автообновления.
* `GET /api/servers.json` — Список активных модпаков для меню выбора в лаунчере.
---
## 5. Алгоритмы и Пайплайны работы
### 5.1. Пайплайн загрузки и сборки модпака (в Админ-панели)
Когда админ загружает ZIP-архив с обновлением модов/конфигов через панель управления:
```text
[Архив с модами] -> [Распаковка во временную папку]
[Обход каждого файла в цикле]
[Расчет SHA-1 хэша]
┌─────────────┴─────────────┐
▼ ▼
[Хэш ЕСТЬ в базе?] [Хэша НЕТ в базе]
│ │
│ [Копирование в CAS]
│ [/files/ab/abcdef12...]
│ │
└─────────────┬─────────────┘
[Добавление в manifest.json]
```
**Итоговый формат генерируемого `manifest.json` для лаунчера:**
```json
{
"minecraft_version": "1.21",
"java_version": 21,
"server_info": {
"name": "HiTech Server",
"ip": "play.myserver.com:25565"
},
"files": [
{
"path": "mods/jei-1.21.jar",
"hash": "a1b2c3d4e5f6...",
"size": 1048576,
"url": "https://cdn.myserver.com/files/a1b2c3d4e5f6..."
},
{
"path": "config/general.json",
"hash": "f7g8h9i0j1k2...",
"size": 1245,
"url": "https://cdn.myserver.com/files/f7g8h9i0j1k2..."
}
]
}
```
### 5.2. Автоматическое добавление сервера в игру (servers.dat)
Перед запуском игры лаунчер читает блок `server_info` из манифеста. Если в `instances/<slug>/servers.dat` отсутствует сервер с IP `server_info.ip`, лаунчер парсит этот NBT-файл и добавляет в него новую запись:
* **Имя:** `server_info.name`
* **IP:** `server_info.ip`
Это избавляет игроков от ручного ввода IP в клиенте.
### 5.3. Пайплайн CI/CD Релиза Лаунчера
```text
[Коммит/Тег в Git] -> [Сборка в CI (Windows/Linux/macOS)]
[Расчет SHA-256 для каждого]
[POST-запрос с секретным токеном]
[Go-Бэкенд сохраняет файлы]
[Обновляет версию лаунчера в БД]
[Игроки видят обновление при запуске]
```
---
## 6. Docker Compose
Все сервисы проекта поднимаются на VPS через Docker Compose. Структура:
### 6.1. Сервисы
#### `backend` — Go-приложение
Собирается как Docker-образ и публикуется в Gitea Container Registry.
```yaml
backend:
image: git.myserver.com/yourname/mc-backend:latest
container_name: mc-backend
restart: unless-stopped
environment:
DATABASE_URL: postgres://mcuser:${DB_PASSWORD}@postgres:5432/mcserver?sslmode=disable
CI_TOKEN: ${CI_TOKEN}
CDN_BASE_URL: https://cdn.myserver.com
volumes:
- cdn_files:/var/www/cdn/files
depends_on:
postgres:
condition: service_healthy
expose:
- "8080"
labels:
- "com.centurylinklabs.watchtower.enable=true"
```
*Переменные `.env` на сервере:*
```
DB_PASSWORD=...
CI_TOKEN=...
```
#### `postgres` — База данных
```yaml
postgres:
image: postgres:16-alpine
container_name: mc-postgres
restart: unless-stopped
environment:
POSTGRES_DB: mcserver
POSTGRES_USER: mcuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mcuser -d mcserver"]
interval: 10s
timeout: 5s
retries: 5
expose:
- "5432"
```
Миграции из папки `./migrations/` применяются автоматически при первом запуске (согласно поведению официального образа PostgreSQL).
#### `caddy` — Reverse Proxy + статика
```yaml
caddy:
image: caddy:2-alpine
container_name: mc-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
- cdn_files:/var/www/cdn/files:ro
```
Caddy автоматически получает и обновляет TLS-сертификаты для всех доменов, указанных в Caddyfile.
#### `watchtower` — Авто-обновление контейнеров
```yaml
watchtower:
image: containrrr/watchtower
container_name: mc-watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_POLL_INTERVAL: 300
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_NOTIFICATIONS: ""
```
Watchtower проверяет наличие новых образов каждые 300 секунд (5 минут). Обновляются только контейнеры с меткой `com.centurylinklabs.watchtower.enable=true`. Старые образы удаляются после успешного обновления (`CLEANUP=true`).
---
### 6.2. Volumes
```yaml
volumes:
pgdata:
driver: local
cdn_files:
driver: local
caddy_data:
driver: local
caddy_config:
driver: local
```
| Volume | Назначение | Доступ |
|---|---|---|
| `pgdata` | Данные PostgreSQL | `postgres` (rw) |
| `cdn_files` | CAS-файлы (моды, ассеты, скины) | `backend` (rw), `caddy` (ro) |
| `caddy_data` | TLS-сертификаты и состояние Caddy | `caddy` (rw) |
| `caddy_config` | Кэш конфигурации Caddy | `caddy` (rw) |
Том `cdn_files` примонтирован read-only в Caddy, чтобы proxy-сервер не мог случайно модифицировать файлы. Только `backend` имеет права на запись.
---
### 6.3. Полный docker-compose.yml
```yaml
services:
caddy:
image: caddy:2-alpine
container_name: mc-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
- cdn_files:/var/www/cdn/files:ro
backend:
image: git.myserver.com/yourname/mc-backend:latest
container_name: mc-backend
restart: unless-stopped
environment:
DATABASE_URL: postgres://mcuser:${DB_PASSWORD}@postgres:5432/mcserver?sslmode=disable
CI_TOKEN: ${CI_TOKEN}
CDN_BASE_URL: https://cdn.myserver.com
volumes:
- cdn_files:/var/www/cdn/files
depends_on:
postgres:
condition: service_healthy
expose:
- "8080"
labels:
- "com.centurylinklabs.watchtower.enable=true"
postgres:
image: postgres:16-alpine
container_name: mc-postgres
restart: unless-stopped
environment:
POSTGRES_DB: mcserver
POSTGRES_USER: mcuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mcuser -d mcserver"]
interval: 10s
timeout: 5s
retries: 5
expose:
- "5432"
watchtower:
image: containrrr/watchtower
container_name: mc-watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_POLL_INTERVAL: 300
WATCHTOWER_LABEL_ENABLE: "true"
volumes:
pgdata:
cdn_files:
caddy_data:
caddy_config:
```
---
## 7. Конфигурация Caddy
Caddy выполняет три роли: reverse proxy для API, отдача CAS-файлов и автоматический HTTPS.
```
# Caddyfile
# CDN — отдача CAS-файлов
cdn.myserver.com {
root * /var/www/cdn/files
@hasPrefix path /files/*
handle /files/* {
file_server {
hide .htaccess
}
header Cache-Control "public, max-age=31536000, immutable"
}
handle /skins/* {
file_server {
hide .htaccess
}
header Cache-Control "public, max-age=3600"
}
}
# API и Yggdrasil
api.myserver.com {
reverse_proxy backend:8080
}
```
**Логика кэширования:**
* **CAS-файлы (`/files/`):** `Cache-Control: max-age=31536000, immutable` — на 1 год, потому что содержимое файла никогда не меняется (меняется только хэш в манифесте).
* **Скины (`/skins/`):** `Cache-Control: max-age=3600` — 1 час, чтобы игроки видели смену скина без жёсткого сброса кэша.
---
## 8. Docker-образы и CI/CD
### 8.1. Тегирование образов
Все образы публикуются в Gitea Container Registry с двумя тегами:
* `latest` — всегда последняя успешная сборка
* `${GIT_COMMIT_SHA:0:8}` — первые 8 символов хэша коммита для отслеживаемости
### 8.2. Dockerfile для backend
```dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /mc-backend ./cmd/server
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /mc-backend .
RUN adduser -D -g '' appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["/app/mc-backend"]
```
Используется multi-stage сборка: финальный образ на базе alpine (~20 MB), без Go toolchains и исходного кода. Приложение запускается от непривилегированного пользователя.
### 8.3. Схема деплоя
```text
[Push в main] → [CI: тесты] → [CI: сборка Docker-образа]
[Push в Gitea Container Registry]
(теги: latest + sha)
[Watchtower на VPS замечает новый образ]
(опрос каждые 5 мин)
[Pull → stop old → start new container]
[Healthcheck PostgreSQL: OK]
[Backend отвечает на :8080]
[Caddy проксирует запросы]
```
---
## 9. Переменные окружения на VPS
Файл `.env` в корне проекта на сервере:
```
DB_PASSWORD=your-secure-password-here
CI_TOKEN=your-ci-secret-token
```
Файл `.env` **не коммитится** в репозиторий. CI_TOKEN также хранится в секретах Gitea (Settings → Secrets) для использования в CI-пайплайне.
---
## 10. Структура проекта на VPS
```
/opt/mc-server/
├── .env # Секретные переменные (не в git)
├── docker-compose.yml
├── Caddyfile
├── migrations/
│ ├── 001_init.sql
│ └── 002_launcher_releases.sql
└── backups/ # Резервные копии PostgreSQL
```
---
## 11. Резервное копирование
Ежедневный dump PostgreSQL через cron на хосте:
```bash
docker exec mc-postgres pg_dump -U mcuser mcserver | gzip > /opt/mc-server/backups/mcserver_$(date +%Y%m%d).sql.gz
```
CAS-файлы рекомендуется бэкапить отдельно (rsync) — при утере их можно восстановить из манифестов модпаков, но займёт время.