20 KiB
Спецификация серверной части 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)
-- Таблица пользователей
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-архив с обновлением модов/конфигов через панель управления:
[Архив с модами] -> [Распаковка во временную папку]
│
▼
[Обход каждого файла в цикле]
│
▼
[Расчет SHA-1 хэша]
│
┌─────────────┴─────────────┐
▼ ▼
[Хэш ЕСТЬ в базе?] [Хэша НЕТ в базе]
│ │
│ [Копирование в CAS]
│ [/files/ab/abcdef12...]
│ │
└─────────────┬─────────────┘
▼
[Добавление в manifest.json]
Итоговый формат генерируемого manifest.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 Релиза Лаунчера
[Коммит/Тег в Git] -> [Сборка в CI (Windows/Linux/macOS)]
│
▼
[Расчет SHA-256 для каждого]
│
▼
[POST-запрос с секретным токеном]
│
▼
[Go-Бэкенд сохраняет файлы]
[Обновляет версию лаунчера в БД]
│
▼
[Игроки видят обновление при запуске]
6. Docker Compose
Все сервисы проекта поднимаются на VPS через Docker Compose. Структура:
6.1. Сервисы
backend — Go-приложение
Собирается как Docker-образ и публикуется в Gitea Container Registry.
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 — База данных
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 + статика
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 — Авто-обновление контейнеров
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
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
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
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. Схема деплоя
[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 на хосте:
docker exec mc-postgres pg_dump -U mcuser mcserver | gzip > /opt/mc-server/backups/mcserver_$(date +%Y%m%d).sql.gz
CAS-файлы рекомендуется бэкапить отдельно (rsync) — при утере их можно восстановить из манифестов модпаков, но займёт время.