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

20 KiB
Raw Blame History

Спецификация серверной части 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) — при утере их можно восстановить из манифестов модпаков, но займёт время.