diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..93906a2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,192 @@ +# MrixsCraft + +Private Minecraft server project with a web-based launcher. + +## Architecture + +Consists of two independent Go repositories unified via git submodules: + +- **`server/`** — backend (Go + net/http + PostgreSQL) +- **`launcher/`** — desktop launcher (Go + Fyne GUI) + +Both are **separate Go modules** — they have their own `go.mod`, tests, and can be built independently. The only connection between them is HTTP API (Yggdrasil + REST). + +Domain: `minecraft.mrixs.me` · CDN: `cdn.mrixs.me` · Registry: `gitea.mrixs.me` + +--- + +## Documentation + +| File | Purpose | +|------|---------| +| `docs/state.md` | **Current project status** — architecture, API, DB schema, TODOs. Read this first. | +| `docs/server/Specification.md` | Server specification (RU) | +| `docs/launcher/Specification.md` | Launcher specification (RU) | + +**Rule:** Read `docs/state.md` first. During work, compare implementation against specifications. Update `docs/state.md` when something changes. + +--- + +## Working with Submodules + +`server/` and `launcher/` are **git submodules**, not regular directories. Each tracks its own remote: + +``` +launcher → ssh://git@gitea.mrixs.me:2222/Mrixs/MrixsCraft-launcher.git +server → ssh://git@gitea.mrixs.me:2222/Mrixs/MrixsCraft-server.git +``` + +### Workflow + +```bash +# 1. Enter submodule +cd server/ # or launcher/ + +# 2. Edit code +# ... + +# 3. Build & test locally +go build ./... +go test ./... + +# 4. Commit INSIDE the submodule +git add -A +git commit -m "feat: ..." -m "Co-Authored-By: Claude Opus 4.8 " + +# 5. Push the submodule +git push origin master + +# 6. (Optional) Return to parent and record the new submodule commit +cd .. +git add server # or launcher +git commit -m "chore: bump server submodule" +``` + +**Always commit and push inside the submodule first.** CI/CD is triggered by pushes to submodule repos, not the parent. + +### Clone + +```bash +git clone --recurse-submodules gitea.mrixs.me:mrixs/mrixscraft.git MC-server +``` + +If already cloned without submodules: +```bash +git submodule update --init --recursive +``` + +--- + +## Server (`server/`) + +### Key Paths + +| Path | Purpose | +|------|---------| +| `cmd/server/main.go` | Entry point — routes, middleware, graceful shutdown | +| `internal/auth/` | Yggdrasil auth (authenticate/refresh/validate) | +| `internal/api/` | Public API (register, login, skins, capes, launcher) | +| `internal/admin/` | Admin panel (modpacks, uploads, manifests) | +| `internal/cas/` | Content-Addressable Storage (SHA-1 file serving) | +| `internal/templates/` | Website pages (embedded via `go:embed`) | +| `migrations/` | SQL migrations (manual apply) | +| `Dockerfile` | Multi-stage build (~20 MB) | +| `.gitea/workflows/ci.yml` | CI: lint → test → build → docker push | + +### Build & Test + +```bash +cd server/ +go build -o mrixscraft-server ./cmd/server +go test ./... -race -cover +``` + +### Environment Variables + +| Variable | Default | Required | +|----------|---------|----------| +| `SERVER_PORT` | `8080` | No | +| `DATABASE_URL` | — | **Yes** | +| `CAS_DIR` | `/var/www/cdn/files` | No | +| `JWT_SECRET` | — | **Yes** | +| `BASE_URL` | `https://minecraft.mrixs.me` | No | + +--- + +## Launcher (`launcher/`) + +### Key Paths + +| Path | Purpose | +|------|---------| +| `cmd/launcher/main.go` | Entry point, Fyne bootstrap | +| `internal/auth/` | Yggdrasil client | +| `internal/config/` | `launcher.json`, system paths | +| `internal/fetcher/` | HTTP downloader with SHA-1 verification | +| `internal/java/` | JRE detection/download | +| `internal/launch/` | Manifest parsing, game launch | +| `internal/selfupdate/` | Binary auto-updater | +| `internal/ui/` | Fyne GUI (screens, components, theme) | + +### Build + +```bash +cd launcher/ +go build -o mrixscraft-launcher ./cmd/launcher +go run ./cmd/launcher +``` + +--- + +## Deployment Flow + +``` +[Commit + push inside submodule] + │ + ▼ + [Gitea Actions CI] + lint → test → build → docker push + │ + ▼ + [Gitea Container Registry] + gitea.mrixs.me/mrixs/mrixscraft-server:latest + │ + ▼ + [Watchtower on VPS] + polls every 5 min → pull → restart +``` + +**No manual deployment needed.** Push to submodule → auto-deploy. + +Required Gitea secrets: `PACKAGES_TOKEN` (PAT with `read:package`, `write:package`). + +--- + +## Style Guidelines + +- Use Markdown (`.md`) for documentation +- Go: stdlib `net/http`, `html/template` with `go:embed`, `pgx/v5` for DB +- Dark theme (#0f0f1a / #16213e) with green accent (#4ade80) for web templates +- Commit message format: `type: description` + +--- + +## Git Conventions + +``` +type: short description + +longer body if needed +``` + +Types: `feat`, `fix`, `chore`, `refactor`, `docs` + +--- + +## Port Reference + +| Service | Port | Scope | +|---------|------|-------| +| server (backend) | 8080 | Internal (Docker) | +| PostgreSQL | 5432 | Internal (Docker) | +| Caddy | 80, 443 | Public | diff --git a/README.md b/README.md new file mode 100644 index 0000000..c388856 --- /dev/null +++ b/README.md @@ -0,0 +1,327 @@ +# MrixsCraft + +Private Minecraft server project with a web-based launcher. Consists of two independent Go repositories unified via git submodules: + +- **`server/`** — backend (Go + net/http + PostgreSQL) +- **`launcher/`** — desktop launcher (Go + Fyne GUI) + +Domain: `minecraft.mrixs.me` · CDN: `cdn.mrixs.me` + +--- + +## Architecture + +``` +┌─────────────┐ HTTP/API ┌──────────────┐ +│ Launcher │ ◄──────────────► │ Server │ +│ (Go+Fyne) │ Yggdrasil+REST │ (Go+pgx) │ +└─────────────┘ └──────┬──────┘ + │ + ┌──────┴──────┐ + │ PostgreSQL │ + │ 16 │ + └─────────────┘ + cdn.mrixs.me minecraft.mrixs.me + ┌──────────┐ ┌────────────────┐ + │ Caddy │ │ Caddy │ + │ file srv │ │ reverse proxy │ + │ (CAS) │ │ + HTTPS │ + └──────────┘ └────────────────┘ +``` + +**Infrastructure (Docker Compose):** Caddy · Go backend · PostgreSQL 16 · Watchtower (auto-update) + +--- + +## Directory Structure + +``` +MC-server/ +├── .gitmodules # Submodules: launcher, server +├── docs/ +│ ├── state.md # Project status and detailed architecture +│ ├── launcher/Specification.md # Launcher specification (RU) +│ └── server/Specification.md # Server specification (RU) +├── launcher/ # Git submodule — MrixsCraft-launcher +│ ├── cmd/launcher/main.go +│ └── internal/ +│ ├── auth/ # Yggdrasil client +│ ├── config/ # Launcher settings & system paths +│ ├── fetcher/ # HTTP downloader with SHA-1 verification +│ ├── java/ # JRE detection/download +│ ├── launch/ # Manifest parsing, game launch, classpath assembly +│ ├── selfupdate/# Binary auto-updater (SHA-256) +│ └── ui/ # Fyne GUI (screens, components, theme) +└── server/ # Git submodule — MrixsCraft-server + ├── cmd/ + │ ├── server/main.go # HTTP routes, middleware, graceful shutdown + │ └── ci-release/main.go # CI release uploader + ├── Dockerfile # Multi-stage (~20 MB) + ├── docker-compose.yml # Caddy + backend + postgres + watchtower + ├── Caddyfile # Reverse proxy, CDN, HTTPS + ├── .gitea/workflows/ci.yml # CI: lint → test → build → docker push + ├── migrations/ + │ ├── 001_init.sql # Full schema: 6 tables + indexes + │ └── 002_migration_history.sql # Migration tracking + └── internal/ + ├── admin/ # Modpack CRUD, file upload, manifests + ├── api/ # Public API: auth, skins, capes, launcher + ├── auth/ # Yggdrasil: authenticate/refresh/validate + ├── cas/ # Content-Addressable Storage (SHA-1) + ├── config/ # ENV configuration + ├── database/ # PostgreSQL (pgx/pgxpool), data models + ├── middleware/ # CORS, Logging, Recovery, RateLimiter + ├── session/ # Background expired-session cleanup + └── templates/ # Go html/template (dark theme) +``` + +--- + +## Tech Stack + +| Component | Technology | +|-----------|-----------| +| Language | Go 1.25 (server) · Go 1.22 (launcher) | +| HTTP | net/http (stdlib) | +| Database | PostgreSQL 16 + pgx/v5 | +| Hashing | SHA-1 (CAS) · SHA-256 (launcher releases) · bcrypt (passwords) | +| GUI | Fyne v2.4.5 | +| Auth | Custom Yggdrasil server + authlib-injector | +| Proxy | Caddy 2 (auto-HTTPS) | +| Containers | Docker + Docker Compose | +| Registry | Gitea Container Registry | +| CI/CD | Gitea Actions | +| Auto-update | Watchtower | + +--- + +## Getting Started + +### Prerequisites + +- Go 1.25+ (server) / Go 1.22+ (launcher) +- Docker & Docker Compose (production) +- PostgreSQL 16 (if running without Docker) + +### Clone with Submodules + +```bash +git clone --recurse-submodules gitea.mrixs.me:mrixs/mrixscraft.git MC-server +``` + +If already cloned without submodules: + +```bash +git submodule update --init --recursive +``` + +### Server + +```bash +cd server/ + +# Configure +cp .env.example .env +# edit .env: DATABASE_URL, JWT_SECRET, etc. + +# Run locally +go run ./cmd/server + +# Or via Docker Compose +docker compose up -d +``` + +**Required environment variables:** + +| Variable | Default | Required | +|----------|---------|----------| +| `SERVER_PORT` | `8080` | No | +| `DATABASE_URL` | — | **Yes** | +| `CAS_DIR` | `/var/www/cdn/files` | No | +| `JWT_SECRET` | — | **Yes** | + +### Launcher + +```bash +cd launcher/ +go build -o mrixscraft-launcher ./cmd/launcher +./mrixscraft-launcher +``` + +Or: `go run ./cmd/launcher` + +--- + +## Server API + +### Yggdrasil (Mojang-compatible) +| Method | Path | Description | +|--------|------|-------------| +| POST | `/authserver/authenticate` | Login with credentials | +| POST | `/authserver/refresh` | Refresh token | +| POST | `/authserver/validate` | Validate token (204) | +| POST | `/authserver/invalidate` | Invalidate token | +| POST | `/authserver/signout` | Sign out (delete all sessions) | +| GET | `/sessionserver/session/minecraft/profile/{uuid}` | Player profile with textures | + +### Public API +| Method | Path | Description | +|--------|------|-------------| +| POST | `/api/web/register` | Player registration | +| POST | `/api/web/login` | Website login | +| POST | `/api/web/profile/skin` | Upload skin (PNG) | +| POST | `/api/web/profile/cape` | Upload cape (PNG) | +| DELETE | `/api/web/profile/skin` | Delete skin | +| GET | `/api/web/profile/{uuid}` | Player profile | + +### Launcher API +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/launcher/latest` | Latest launcher version | +| GET | `/api/servers.json` | Active modpack list | +| GET | `/api/instances/{slug}/manifest.json` | Modpack manifest | + +### CAS (File Server) +| Method | Path | Description | +|--------|------|-------------| +| GET | `/files/{sha1}` | File by SHA-1 hash | +| GET | `/files/launcher/{version}/{os}/{arch}/{filename}` | Launcher binary | +| GET | `/skins/{hash}` | Skin/cape by hash | + +### Admin (Bearer token + role=admin) +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/admin/modpacks` | List modpacks | +| POST | `/api/admin/modpacks` | Create modpack | +| PUT | `/api/admin/modpacks/{id}` | Update modpack | +| DELETE | `/api/admin/modpacks/{id}` | Deactivate modpack | +| POST | `/api/admin/modpacks/{slug}/upload` | Upload files (≤500 MB) | +| POST | `/api/admin/modpacks/{slug}/manifest` | Generate manifest.json | +| POST | `/api/admin/launcher/release` | Upload launcher release (X-CI-Token) | +| GET | `/admin` | Web admin interface (requires admin role) | + +--- + +## Content-Addressable Storage (CAS) + +All files (mods, libraries, assets) are stored by their SHA-1 hash: + +``` +/var/www/cdn/files/ab/abcdef1234... (first 2 chars as subdirectory) +``` + +- **Immutable** — files are never overwritten +- **Cache-Control:** `public, max-age=31536000, immutable` (1 year) +- **Deduplication** — automatic (same hash = same file) +- **Concurrent-safe** — per-hash `sync.Mutex` prevents race conditions +- **Verification** — constant-time SHA-1 comparison + +--- + +## Database Schema + +7 tables: + +| Table | Purpose | +|-------|---------| +| `users` | Players (username, email, password_hash, uuid, role) | +| `player_textures` | Skins & capes (skin_hash, cape_hash → CAS) | +| `yggdrasil_sessions` | Auth sessions (access_token, client_token, expires_at) | +| `modpacks` | Modpacks/servers (slug, name, minecraft_version, java_version, server_ip) | +| `global_files` | CAS file registry (sha1 PK, size_bytes, file_name, mime_type) | +| `launcher_releases` | Launcher releases (version, os, arch, sha256, file_path) | +| `migration_history` | Applied migration tracking | + +Migrations are applied manually: `psql $DATABASE_URL -f migrations/001_init.sql` + +--- + +## Launcher Client File Structure + +| OS | Root Directory | +|----|---------------| +| Windows | `%APPDATA%\MrixsCraft\` | +| macOS | `~/Library/Application Support/MrixsCraft/` | +| Linux | `~/.MrixsCraft/` | + +``` +MrixsCraft/ +├── launcher.json # Settings (RAM, server URL, window) +├── session.json # Yggdrasil tokens +├── authlib-injector.jar # Auth interceptor +├── Java/{8,17,21}/ # Portable JREs +├── assets/ # Game assets +├── libraries/ # Shared libraries (LWJGL, etc.) +└── instances/{slug}/ # Isolated modpack clients + ├── mods/ + ├── mods_backup/ # Unknown mods moved here (soft delete) + ├── config/ + └── resourcepacks/ +``` + +--- + +## Testing + +```bash +# Server +cd server/ +go test ./... -v -race -cover + +# Launcher — no tests (GUI testing is impractical) +``` + +Server test coverage: `cas`, `auth`, `api`, `session` packages. + +--- + +## CI/CD + +Gitea Actions pipeline (`.gitea/workflows/ci.yml`): + +``` +lint (go vet + gofmt) → test (race detector) → build → docker push (master only) +``` + +Images: `gitea.mrixs.me/mrixs/mrixscraft-server:latest` + `:sha` + +Watchtower on VPS polls every 5 minutes and auto-deploys new images. + +--- + +## Known TODOs + +### Server +- Full-text file search in DB +- SIGHUP config reload (requires `config.Atomic` refactor) + +### Launcher +- PLAY button wired to `launch.Prepare` + `Game.Start` (stub) +- Server list hardcoded (not loaded from `/api/servers.json`) +- Java auto-download not implemented +- Skin avatar rendering (8×64 face crop) +- News/patchnotes on MainScreen +- `servers.dat` NBT manipulation + +### Infrastructure +- CI deploy step (SSH + docker compose up) — needs secrets +- Automatic Go migration runner (currently manual) +- Backup script not in cron + +--- + +## Commands Reference + +```bash +# Server +cd server/ +go build -o mrixscraft-server ./cmd/server +go test ./... +go run ./cmd/server +docker compose up -d + +# Launcher +cd launcher/ +go build -o mrixscraft-launcher ./cmd/launcher +go run ./cmd/launcher +``` diff --git a/docs/server/Specification.md b/docs/server/Specification.md index bf16180..5ed0fe0 100644 --- a/docs/server/Specification.md +++ b/docs/server/Specification.md @@ -1,14 +1,19 @@ # Спецификация серверной части Minecraft проекта (Backend & Infrastructure) ## 1. Стек технологий -* **Бэкенд-платформа:** Go (чистый net/http с роутером github.com/go-chi/chi, для максимальной прозрачности и контроля). + +* **Бэкенд-платформа:** Go (чистый net/http со стандартным роутером, для максимальной прозрачности и контроля, без внешних зависимостей). * **База данных:** PostgreSQL (реляционная СУБД для надежного хранения транзакций токенов, пользователей и связей файлов). * **Фронтенд (Сайт и Админка):** SPA на Vue.js / React / Svelte, либо классический монолит на Go-шаблонах (html/template) для максимального упрощения деплоя (всё в одном исполняемом файле). -* **Статический веб-сервер (CDN):** Nginx (занимается отдачей тяжелых файлов и проксированием API-запросов к Go-бэкенду). +* **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/[первые два символа хэша]/[полный хэш]` @@ -85,7 +90,9 @@ CREATE TABLE launcher_releases ( ## 4. Спецификация API Эндпоинтов ### 4.1. Автоматический деплой из CI/CD (Protected Route) + Эндпоинт, который вызывает твой пайплайн сборки после успешной компиляции лаунчера. + * **Маршрут:** `POST /api/admin/launcher/release` * **Авторизация:** Заголовок `X-CI-Token: <секретный_токен_из_секретов_репозитория>` * **Тело запроса (Multipart Form Data):** @@ -96,12 +103,14 @@ CREATE TABLE launcher_releases ( * `file`: (бинарный файл лаунчера) ### 4.2. Yggdrasil API (Mojang Emulation) -* `POST /authserver/authenticate` — Аутентификация по логину/паролю. Возвращает UUID и токены. + +* `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/` и привязывает к юзеру). @@ -164,9 +173,11 @@ CREATE TABLE launcher_releases ( ``` ### 5.2. Автоматическое добавление сервера в игру (servers.dat) + Перед запуском игры лаунчер читает блок `server_info` из манифеста. Если в `instances//servers.dat` отсутствует сервер с IP `server_info.ip`, лаунчер парсит этот NBT-файл и добавляет в него новую запись: * **Имя:** `server_info.name` * **IP:** `server_info.ip` + Это избавляет игроков от ручного ввода IP в клиенте. ### 5.3. Пайплайн CI/CD Релиза Лаунчера @@ -190,10 +201,334 @@ CREATE TABLE launcher_releases ( --- -## 6. Конфигурация Nginx (CDN & Static) +## 6. Docker Compose -Для максимальной производительности Nginx настраивается на прямую отдачу файлов, минуя Go-бэкенд. Запросы к API проксируются. +Все сервисы проекта поднимаются на VPS через Docker Compose. Структура: -* **Статика (`/files/`)**: Настраивается отдавать файлы с вечным кэшированием, так как они никогда не меняются (меняется только сам хэш в манифесте). -* **Скины (`/skins/`)**: Кэширование настраивается на меньший срок (например, 1 час), чтобы при смене скина игроки видели изменения без жесткого сброса кэша. -* **API (`/api/`, `/authserver/`)**: Все запросы перенаправляются на локальный порт Go-приложения (например, `127.0.0.1:8080`). +### 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) — при утере их можно восстановить из манифестов модпаков, но займёт время. diff --git a/docs/state.md b/docs/state.md new file mode 100644 index 0000000..b44b5a8 --- /dev/null +++ b/docs/state.md @@ -0,0 +1,462 @@ +# 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) + +Путь хранения: `//` +Пример: `/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 (1024–16384 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=` + - 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 +``` diff --git a/launcher b/launcher index 320f009..e927fff 160000 --- a/launcher +++ b/launcher @@ -1 +1 @@ -Subproject commit 320f009658f5cffff6445d89d7e8cadb7dd83b57 +Subproject commit e927fff02fe4dfc5cddfdda86063acda5a5d95cb diff --git a/server b/server index 551c75a..7a8e791 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 551c75a232d06a374cd36c878ac37489d62803b4 +Subproject commit 7a8e79123f971c74c6f67b7fa6b2ebdb2cfe27dd