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

463 lines
25 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.
# MrixsCraft — Текущее состояние проекта
> Дата: 2026-06-05
> Версия: dev (pre-alpha)
---
## 1. Обзор проекта
MrixsCraft — приватный Minecraft-сервер с веб-лаунчером. Проект состоит из двух независимых репозиториев, объединённых через git submodules:
- **`server/`** — бэкенд на Go (net/http + PostgreSQL)
- **`launcher/`** — десктопный лаунчер на Go + Fyne GUI
Домен: `minecraft.mrixs.me`
CDN: `cdn.mrixs.me`
Registry: `gitea.mrixs.me/mrixs/mrixscraft-server`
---
## 2. Структура файлов
```
MC-server/
├── .gitmodules # Submodules: launcher, server
├── docs/
│ ├── server/Specification.md # Спецификация серверной части (RU)
│ └── launcher/Specification.md # Спецификация лаунчера (RU)
├── launcher/ # Git submodule (MrixsCraft-launcher)
│ ├── go.mod # Module: gitea.mrixs.me/Mrixs/MrixsCraft-launcher (Go 1.22)
│ ├── go.sum
│ ├── README.md
│ ├── cmd/launcher/main.go # Точка входа лаунчера
│ ├── internal/
│ │ ├── auth/auth.go # Yggdrasil клиент (authenticate/refresh/validate)
│ │ ├── config/config.go # Настройки лаунчера, системные пути
│ │ ├── fetcher/fetcher.go # HTTP-загрузчик файлов с SHA-1 верификацией
│ │ ├── java/java.java # Поиск/загрузка JRE
│ │ ├── launch/
│ │ │ ├── launch.go # Запуск Minecraft (classpath, аргументы, exec)
│ │ │ └── manifest.go # Парсинг manifest.json
│ │ ├── selfupdate/selfupdate.go # Автообновление лаунчера
│ │ └── ui/
│ │ ├── ui.go # Fyne bootstrap, главное окно
│ │ ├── screens/screens.go # Экраны (Main, Login, Settings)
│ │ ├── components/components.go # Виджеты (ServerCard, PlayButton, Avatar)
│ │ └── theme/theme.go # Minecraft-стилизация Fyne
│ └── pkg/utils/utils.go # SHA1File, SHA1Bytes, Unzip
└── server/ # Git submodule (MrixsCraft-server)
├── go.mod # Module: gitea.mrixs.me/Mrixs/MrixsCraft-server (Go 1.25)
├── go.sum
├── README.md
├── Dockerfile # Multi-stage build (~20 MB, golang:1.25-alpine + alpine:3.19)
├── docker-compose.yml # Caddy + backend + postgres + watchtower
├── Caddyfile # Reverse proxy, CDN, HTTPS
├── .env.example # Шаблон переменных окружения
├── .gitignore
├── .gitea/
│ └── workflows/
│ └── ci.yml # CI: lint → test → build → docker push
├── migrations/
│ ├── README.md # Инструкция по применению миграций
│ ├── 001_init.sql # Полная схема БД (6 таблиц + индексы)
│ └── 002_migration_history.sql # Таблица отслеживания миграций
├── cmd/
│ ├── server/main.go # Точка входа: маршруты, middleware, graceful shutdown
│ └── ci-release/main.go # CLI-утилита для загрузки релиза лаунчера из CI
├── internal/
│ ├── admin/admin.go # CRUD модпаков, загрузка файлов, манифесты, launcher release
│ ├── api/api.go # Публичное API: регистрация, логин, скины, плащи, launcher
│ ├── auth/auth.go # Yggdrasil протокол: authenticate/refresh/validate/invalidate
│ ├── cas/cas.go # Content-Addressable Storage: отдача файлов по SHA-1 хэшу
│ ├── config/config.go # Конфигурация из ENV (порт, БД, CAS, JWT, CI token)
│ ├── database/database.go # PostgreSQL (pgx/pgxpool), модели данных
│ ├── middleware/middleware.go # CORS, Logging, Recovery, RateLimiter
│ ├── session/cleanup.go # Фоновая очистка expired-сессий
│ └── templates/
│ ├── templates.go # Go html/template: per-page parsing (base+page pair)
│ └── html/
│ ├── base.html # Базовый layout (тёмная тема, зелёный акцент)
│ ├── index.html # Главная страница сервера
│ ├── login.html # Форма входа (POST → /api/web/login)
│ ├── register.html # Форма регистрации (POST → /api/web/register)
│ └── profile.html # Профиль игрока (скины, плащи, лаунчер)
└── pkg/utils/utils.go # SHA1Bytes, SHA256Bytes, SHA1File, WriteJSON, WriteError, Unzip
```
---
## 3. Серверная часть (server/)
### 3.1. Технологический стек
| Компонент | Технология |
|-----------|-----------|
| Язык | Go 1.25 |
| HTTP | net/http (стандартная библиотека) |
| База данных | PostgreSQL 16 + pgx/v5 |
| Аутентификация | bcrypt, crypto/rand токены |
| Хеширование | SHA-1 (CAS), SHA-256 (релизы лаунчера) |
| Контейнеризация | Docker multi-stage build |
| Reverse proxy | Caddy 2 |
| Автообновление | Watchtower + Gitea Container Registry |
| CI/CD | Gitea Actions (.gitea/workflows/ci.yml) |
**Зависимости go.mod:**
- `github.com/jackc/pgx/v5 v5.6.0` — PostgreSQL драйвер
- `golang.org/x/crypto` — bcrypt для хеширования паролей
### 3.2. База данных
Файлы миграций: `server/migrations/`
| Миграция | Назначение |
|----------|-----------|
| `001_init.sql` | Начальная схема: 6 таблиц + 9 индексов |
| `002_migration_history.sql` | Таблица для отслеживания применённых миграций |
| `README.md` | Инструкция по ручному применению |
**7 таблиц:**
| Таблица | Назначение |
|---------|-----------|
| `users` | Пользователи (username, email, password_hash, uuid, role) |
| `player_textures` | Скины и плащи (skin_hash, cape_hash → CAS) |
| `yggdrasil_sessions` | Сессии авторизации (access_token, client_token, expires_at) |
| `modpacks` | Модпаки/серверы (slug, name, minecraft_version, java_version, server_ip) |
| `global_files` | CAS-реестр файлов (sha1 PK, size_bytes, file_name, mime_type) |
| `launcher_releases` | Релизы лаунчера (version, os, arch, sha256, file_path) |
| `migration_history` | Отслеживание применённых миграций (filename, applied_at) |
**Индексы:** 9 индексов для быстрого поиска по токенам, UUID, username, email, role, файлам.
### 3.3. API Endpoints
#### Yggdrasil (Mojang-совместимый)
| Метод | Путь | Описание |
|-------|------|----------|
| POST | `/authserver/authenticate` | Аутентификация по логину/паролю |
| POST | `/authserver/refresh` | Обновление токена |
| POST | `/authserver/validate` | Проверка токена (204 No Content) |
| POST | `/authserver/invalidate` | Инвалидация токена |
| POST | `/authserver/signout` | Выход (удаление всех сессий пользователя) |
| GET | `/sessionserver/session/minecraft/profile/{uuid}` | Профиль игрока с текстурами |
#### Публичное API (сайт)
| Метод | Путь | Описание |
|-------|------|----------|
| POST | `/api/web/register` | Регистрация игрока (email через net/mail.ParseAddress) |
| POST | `/api/web/login` | Логин на сайт |
| POST | `/api/web/profile/skin` | Загрузка скина (PNG, валидация размеров) |
| POST | `/api/web/profile/cape` | Загрузка плаща (PNG) |
| DELETE | `/api/web/profile/skin` | Удаление скина |
| DELETE | `/api/web/profile/cape` | Удаление плаща |
| GET | `/api/web/profile/{uuid}` | Профиль игрока (UUID, username, текстуры) |
#### Лаунчер
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/api/launcher/latest` | Последняя версия лаунчера (?os=&arch=) |
| GET | `/api/servers.json` | Список активных модпаков |
| GET | `/api/instances/{slug}/manifest.json` | Манифест модпака |
#### Файловый сервер (CAS)
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/files/{hash}` | Файл по SHA-1 хэшу (40 hex chars) |
| GET | `/files/launcher/{version}/{os}/{arch}/{filename}` | Бинарник лаунчера |
| GET | `/skins/{hash}` | Скин/плащ по хэшу |
#### Веб-шаблоны (HTML)
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/` | Главная страница (Minecraft-стиль) |
| GET | `/login` | Страница логина (форма → /api/web/login) |
| GET | `/register` | Страница регистрации (форма → /api/web/register) |
| GET | `/profile` | Профиль игрока (скины, плащи, скачивание лаунчера) |
#### Админ-панель (Bearer token + role=admin)
| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/api/admin/modpacks` | Список модпаков |
| POST | `/api/admin/modpacks` | Создать модпак |
| PUT | `/api/admin/modpacks/{id}` | Обновить модпак |
| DELETE | `/api/admin/modpacks/{id}` | Деактивировать модпак |
| POST | `/api/admin/modpacks/{slug}/upload` | Загрузка файлов (multipart, до 500 MB) |
| POST | `/api/admin/modpacks/{slug}/manifest` | Генерация manifest.json |
| POST | `/api/admin/launcher/release` | Загрузка релиза лаунчера (X-CI-Token) |
| GET | `/admin` | Веб-интерфейс админ-панели (требует роль admin) |
### 3.4. Middleware цепочки
```
Recovery → Logging → RateLimit → CORS → mux
(outermost) (innermost)
```
Порядок применения (от внешнего к внутреннему):
1. **Recovery** — catch panics → 500 + stack trace log
2. **Logging** — method, path, status, duration, remote addr
3. **RateLimit** — per-IP token bucket (30 req/min, burst 60)
4. **CORS**`Access-Control-Allow-*` headers + OPTIONS handling
### 3.5. Content-Addressable Storage (CAS)
Путь хранения: `<CASDir>/<first_2_chars_of_hash>/<full_40_char_hash>`
Пример: `/var/www/cdn/files/a1/a1b2c3d4e5...`
- Иммутабельность: файлы никогда не перезаписываются
- Cache-Control: `public, max-age=31536000, immutable` (1 год)
- Content-Type определяется по расширению оригинального имени файла (из `global_files.file_name`)
- Верификация: `VerifyAndStore` — сравнивает SHA-1 загруженных данных с ожидаемым хэшем (constant-time)
- **Конкурентная безопасность:** per-hash `sync.Mutex` предотвращает race condition при параллельной записи одного файла. `StoreFile` идемпотентен — если файл уже записан другим воркером, возвращает существующий hash.
Экспортируемые функции (`cas` пакет):
- `StoreFile(casDir, data) → (hash, error)` — сохранение в CAS (потокобезопасен)
- `FileExists(casDir, hash) → bool` — проверка наличия
- `VerifyAndStore(casDir, data, expectedHash) → (hash, error)` — верификация + сохранение
### 3.6. Валидация email
Реализована через стандартную библиотеку `net/mail.ParseAddress()`:
- Проверка длины: ≤ 254 символов (RFC 5321)
- Синтаксический разбор: `mail.ParseAddress()` (RFC 5322)
- Отклоняет адреса типа `a@b.`, `user@`, `@domain.com`
### 3.7. CI/CD Pipeline
Файл: `.gitea/workflows/ci.yml` (Gitea Actions, совместим с GitHub Actions).
| Шаг | Что делает | Условие |
|-----|-----------|---------|
| `lint` | `go vet ./...` + `gofmt -l .` | Всегда |
| `test` | `go test ./... -v -race -cover` | После lint |
| `build` | `go build -o mrixscraft-server ./cmd/server` | После test |
| `docker` | `docker build` + `push` в реестр | Только `master` ветка |
Registry: `gitea.mrixs.me/mrixs/mrixscraft-server:latest`, `:sha`
**Docker аутентификация:** PAT (Personal Access Token) через `secrets.PACKAGES_TOKEN` + `gitea.repository_owner`. GITHUB_TOKEN read-only для packages в Gitea (issue #23642).
**Required secrets:**
- `PACKAGES_TOKEN` — PAT с scopes: `read:package`, `write:package`
### 3.8. Переменные окружения
| Переменная | По умолчанию | Обязательная |
|------------|-------------|--------------|
| `SERVER_PORT` | 8080 | Нет |
| `DATABASE_URL` | — | **Да** |
| `CAS_DIR` | `/var/www/cdn/files` | Нет |
| `SKINS_DIR` | `/var/www/cdn/skins` | Нет |
| `JWT_SECRET` | — | **Да** |
| `CI_SECRET` | — | Нет |
| `BASE_URL` | `https://minecraft.mrixs.me` | Нет |
### 3.9. Docker Compose сервисы
| Сервис | Образ | Роль |
|--------|-------|------|
| `caddy` | caddy:2-alpine | Reverse proxy, HTTPS, статика |
| `backend` | gitea.mrixs.me/mrixs/mrixscraft-server:latest | Go-приложение, порт 8080 |
| `postgres` | postgres:16-alpine | БД, healthcheck, авто-миграции |
| `watchtower` | containrrr/watchtower | Авто-обновление контейнеров (5 мин) |
Volumes: `pgdata`, `cdn_files` (rw для backend, ro для caddy), `caddy_data`, `caddy_config`
---
## 4. Лаунчер (launcher/)
### 4.1. Технологический стек
| Компонент | Технология |
|-----------|-----------|
| Язык | Go 1.22 |
| GUI | Fyne v2.4.5 |
| Авторизация | Yggdrasil API (серверная часть) |
| Хеширование | SHA-1 (файлы), SHA-256 (автообновление) |
**Зависимости go.mod:**
- `fyne.io/fyne/v2 v2.4.5` — GUI фреймворк
### 4.2. Архитектура пакетов
| Пакет | Назначение |
|-------|-----------|
| `internal/auth` | Yggdrasil клиент: authenticate, refresh, validate, ensureValid, сохранение session.json |
| `internal/config` | launcher.json (ServerURL, MemoryMB, ExtraArgs, окно), системные пути |
| `internal/fetcher` | Download() с SHA-1 верификацией, WorkerPool (4 воркера) |
| `internal/java` | Поиск JRE (JavaDir/version/bin/java), заглушка для автозагрузки |
| `internal/launch` | Manifest (парсинг), Game (Prepare, BuildCommand, Start, cleanupUnknownMods) |
| `internal/selfupdate` | Check(), Apply(), Restart() — SHA-256 верификация, rename для Windows |
| `internal/ui` | Fyne bootstrap, главное окно |
| `internal/ui/screens` | MainScreen, LoginScreen, SettingsScreen |
| `internal/ui/components` | ServerCard, PlayButton, SettingsButton, LogoutButton, AvatarImage, ProgressBar |
| `internal/ui/theme` | MinecraftTheme — тёмная тема, зелёный акцент |
| `pkg/utils` | SHA1File, SHA1Bytes, Unzip |
### 4.3. Жизненный цикл лаунчера
```
main() → config.EnsureRoot() → config.Load() → auth.NewFromConfig() → EnsureValid() → ui.Launch()
```
1. Загрузка настроек из `launcher.json` (или defaults)
2. Проверка сессии: `validate``refresh` → если неудачно → показ экрана логина
3. GUI: BorderLayout (left=серверы, center=контент, bottom=управление)
4. Выбор сервера → MainScreen
5. PLAY → (заглушка, TODO: launch.Prepare → Game.Start)
6. Настройки: слайдер RAM (102416384 MB), дополнительные JVM-флаги
### 4.4. Файловая структура клиента
| ОС | Корневая директория |
|----|---------------------|
| Windows | `%APPDATA%\MrixsCraft\` |
| macOS | `~/Library/Application Support/MrixsCraft/` |
| Linux | `~/.MrixsCraft/` |
Содержимое: `launcher.json`, `session.json`, `Java/{8,17,21}/`, `assets/`, `libraries/`, `instances/{slug}/` (mods, config, resourcepacks, mods_backup)
### 4.5. Запуск игры (`launch`)
1. **Prepare:**
- Скачивание манифеста с сервера
- LoadManifest → парсинг JSON
- Java.Find(version) → поиск или (TODO) загрузка JRE
- WorkerPool(4) → параллельная загрузка файлов с SHA-1 проверкой
- cleanupUnknownMods → неизвестные моды в mods_backup/
2. **BuildCommand:**
- JVM args: `-Xms/-Xmx` (RAM), пользовательские аргументы
- Authlib-injector: `-javaagent:authlib-injector.jar=<URL>`
- Classpath: все .jar из манифеста + `libraries/*`
- Game args: интерполяция `${player_name}`, `${auth_uuid}`, `${auth_access_token}`, и т.д.
3. **Start:** `exec.Command(java, args...).Run()`
---
## 5. Общие утилиты
### 5.1. `server/pkg/utils/`
Функции:
- `SHA1Bytes(data) → string` — SHA-1 hex digest
- `SHA256Bytes(data) → string` — SHA-256 hex digest
- `SHA1File(path) → (string, error)` — SHA-1 файла
- `WriteJSON(w, status, v)` — HTTP JSON response
- `WriteError(w, status, msg)` — HTTP JSON error (`{"error": msg}`)
- `Unzip(data, dest) → ([]string, error)` — распаковка ZIP с zip-slip защитой
Потребители: `internal/auth`, `internal/admin`, `internal/api`, `internal/cas`
### 5.2. `launcher/pkg/utils/`
Функции:
- `SHA1File(path) → (string, error)` — SHA-1 файла
- `SHA1Bytes(data) → string` — SHA-1 hex digest
- `Unzip(src, dest) → error` — распаковка ZIP-файла с zip-slip защитой
Потребитель: `internal/fetcher`
**Примечание:** Утилиты SHA-1/SHA-256/Unzip дублируются между сервером и лаунчером (разные модули). Это нормально, т.к. лаунчер — отдельный модуль.
---
## 6. Тестирование
### 6.1. Серверные тесты
| Файл | Пакет | Что тестирует |
|------|-------|--------------|
| `internal/cas/cas_test.go` | `cas` | `isValidHash`, `StoreFile`, `FileExists`, `VerifyAndStore`, `detectContentType`, `StoreFile_ConcurrentSameHash` |
| `internal/auth/auth_test.go` | `auth` | `GenerateToken`, `GenerateUUID`, `HashPassword`, `VerifyPassword`, `IsBcryptHash`, `ExtractBearer` |
| `internal/api/api_test.go` | `api` | Валидация register/login (email, длина, пустые поля), параметры launcherLatest, auth middleware, регистрация маршрутов |
| `internal/session/cleanup_test.go` | `session` | `StartCleanupWorker` с nil DB (graceful no-op) |
Тесты используют `testing` + `httptest`. DB-интеграционные тесты отсутствуют (ручное тестирование).
### 6.2. Тесты лаунчера
Отсутствуют (тестирование GUI затруднено).
---
## 7. Известные TODO и незавершённые части
### Сервер
- [ ] **Поиск по БД**: API не имеет полнотекстового поиска файлов.
- [ ] **Автодеплой релизов лаунчера**: CI собирает и пушит Docker-образ, но не вызывает `POST /api/admin/launcher/release`. Обработчик на сервере и CLI-утилита (`cmd/ci-release/`) есть, но не подключены к CI-пайплайну.
- [ ] **SIGHUP reload**: Reload конфига без рестарта — отложен (требует рефакторинга handler'ов на `config.Atomic`).
### Лаунчер
- [ ] **PLAY кнопка**: Привязка к `launch.Prepare` + `Game.Start` не реализована (заглушка).
- [ ] **Server list**: Список серверов hardcoded (`hitech`, `vanilla`), не загружается с `/api/servers.json`.
- [ ] **Java auto-download**: `java.Find()` возвращает ошибку вместо загрузки JRE.
- [ ] **Автообновление**: `selfupdate.Check()` вызывается только в `main.go` если `version != "dev"`.
- [ ] **Аватарка**: 8x64 лицо из скина не рендерится (заглушка — пустое изображение).
- [ ] **Новости/патчноуты**: TODO в MainScreen.
- [ ] **servers.dat**: Добавление сервера в NBT-файл не реализовано.
### Инфраструктура
- [ ] **Миграции**: Ручное применение (нет автоматического Go-мигратора).
- [ ] **backup скрипт**: Не добавлен в cron (только описан в спецификации).
- [ ] **Graceful restart лаунчера**: Windows `.old` rename не протестирован.
---
## 8. Архитектурные заметки
### Паттерны
- **handler-per-domain**: Каждый домен (auth, admin, api, cas, templates) имеет свой `Handler` с методами `NewHandler(db, cfg)` и `RegisterRoutes(mux)`.
- **Shared utilities**: `pkg/utils` содержит общие HTTP и crypto функции, используемые несколькими пакетами.
- **Middleware chain**: Функциональная композиция через `var handler http.Handler = mux`.
- **CAS**: Content-Addressable Storage — файлы хранятся по хэшу, дедупликация автоматическая. Per-hash mutex для конкурентной безопасности.
- **Per-page templates**: Каждая HTML-страница парсит `base.html` + свой файл отдельно (`template.New("base.html").ParseFS(..., "html/base.html", "html/page.html")`), хранится в `map[string]*template.Template`. Это предотвращает перезапись `{{define "content"}}` блоков при wildcard-парсинге (баг исправлен 2026-06-04).
### Безопасность
- bcrypt для паролей (cost=default)
- SHA-1 для CAS (не криптографический контекст — целостность файлов)
- ConstantTimeCompare для хэш-сравнений
- Bearer token для API авторизации
- X-CI-Token для CI/CD эндпоинта (constant-time comparison)
- Path traversal защита в CAS и launcher asset serving
- Zip-slip защита при распаковке
- Rate limiting (token bucket, per-IP)
- Recovery middleware (panic → 500)
- Email validation через net/mail.ParseAddress (RFC 5322)
### Разделение модулей
- `server/` и `launcher/`**разные Go модули** (разные `go.mod`).
- Лаунчер может собираться и работать автономно.
- Единственная связь — HTTP API (Yggdrasil + REST).
---
## 9. Команды
### Сервер
```bash
cd server/
go build -o mrixscraft-server ./cmd/server
go test ./...
go run ./cmd/server
```
### Лаунчер
```bash
cd launcher/
go build -o mrixscraft-launcher ./cmd/launcher
go run ./cmd/launcher
```
### Docker
```bash
cd server/
docker compose up -d
```