Update main repo
This commit is contained in:
192
CLAUDE.md
Normal file
192
CLAUDE.md
Normal file
@@ -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 <noreply@anthropic.com>"
|
||||||
|
|
||||||
|
# 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 |
|
||||||
327
README.md
Normal file
327
README.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
# Спецификация серверной части Minecraft проекта (Backend & Infrastructure)
|
# Спецификация серверной части Minecraft проекта (Backend & Infrastructure)
|
||||||
|
|
||||||
## 1. Стек технологий
|
## 1. Стек технологий
|
||||||
* **Бэкенд-платформа:** Go (чистый net/http с роутером github.com/go-chi/chi, для максимальной прозрачности и контроля).
|
|
||||||
|
* **Бэкенд-платформа:** Go (чистый net/http со стандартным роутером, для максимальной прозрачности и контроля, без внешних зависимостей).
|
||||||
* **База данных:** PostgreSQL (реляционная СУБД для надежного хранения транзакций токенов, пользователей и связей файлов).
|
* **База данных:** PostgreSQL (реляционная СУБД для надежного хранения транзакций токенов, пользователей и связей файлов).
|
||||||
* **Фронтенд (Сайт и Админка):** SPA на Vue.js / React / Svelte, либо классический монолит на Go-шаблонах (html/template) для максимального упрощения деплоя (всё в одном исполняемом файле).
|
* **Фронтенд (Сайт и Админка):** 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)
|
## 2. Архитектура хранилища файлов (Content-Addressable Storage - CAS)
|
||||||
|
|
||||||
Для экономии места на диске сервера и клиента, а также для сквозного кэширования, все файлы модов, библиотек и ассетов хранятся по их SHA-1 хэшам.
|
Для экономии места на диске сервера и клиента, а также для сквозного кэширования, все файлы модов, библиотек и ассетов хранятся по их SHA-1 хэшам.
|
||||||
|
|
||||||
* **Путь на сервере:** `/var/www/cdn/files/[первые два символа хэша]/[полный хэш]`
|
* **Путь на сервере:** `/var/www/cdn/files/[первые два символа хэша]/[полный хэш]`
|
||||||
@@ -85,7 +90,9 @@ CREATE TABLE launcher_releases (
|
|||||||
## 4. Спецификация API Эндпоинтов
|
## 4. Спецификация API Эндпоинтов
|
||||||
|
|
||||||
### 4.1. Автоматический деплой из CI/CD (Protected Route)
|
### 4.1. Автоматический деплой из CI/CD (Protected Route)
|
||||||
|
|
||||||
Эндпоинт, который вызывает твой пайплайн сборки после успешной компиляции лаунчера.
|
Эндпоинт, который вызывает твой пайплайн сборки после успешной компиляции лаунчера.
|
||||||
|
|
||||||
* **Маршрут:** `POST /api/admin/launcher/release`
|
* **Маршрут:** `POST /api/admin/launcher/release`
|
||||||
* **Авторизация:** Заголовок `X-CI-Token: <секретный_токен_из_секретов_репозитория>`
|
* **Авторизация:** Заголовок `X-CI-Token: <секретный_токен_из_секретов_репозитория>`
|
||||||
* **Тело запроса (Multipart Form Data):**
|
* **Тело запроса (Multipart Form Data):**
|
||||||
@@ -96,12 +103,14 @@ CREATE TABLE launcher_releases (
|
|||||||
* `file`: (бинарный файл лаунчера)
|
* `file`: (бинарный файл лаунчера)
|
||||||
|
|
||||||
### 4.2. Yggdrasil API (Mojang Emulation)
|
### 4.2. Yggdrasil API (Mojang Emulation)
|
||||||
* `POST /authserver/authenticate` — Аутентификация по логину/паролю. Возвращает UUID и токены.
|
|
||||||
|
* `POST /authserver/authenticate` — Аутентификация по логину/пароля. Возвращает UUID и токены.
|
||||||
* `POST /authserver/refresh` — Обновление сессии лаунчера по `clientToken` и `accessToken`.
|
* `POST /authserver/refresh` — Обновление сессии лаунчера по `clientToken` и `accessToken`.
|
||||||
* `POST /authserver/validate` — Быстрая проверка токена при запуске лаунчера.
|
* `POST /authserver/validate` — Быстрая проверка токена при запуске лаунчера.
|
||||||
* `GET /sessionserver/session/minecraft/profile/{uuid}` — Эндпоинт, к которому обращается сам игровой клиент для загрузки скинов игроков на сервере.
|
* `GET /sessionserver/session/minecraft/profile/{uuid}` — Эндпоинт, к которому обращается сам игровой клиент для загрузки скинов игроков на сервере.
|
||||||
|
|
||||||
### 4.3. Публичное API сайта и лаунчера
|
### 4.3. Публичное API сайта и лаунчера
|
||||||
|
|
||||||
* `POST /api/web/register` — Регистрация игрока.
|
* `POST /api/web/register` — Регистрация игрока.
|
||||||
* `POST /api/web/login` — Логин в личный кабинет сайта.
|
* `POST /api/web/login` — Логин в личный кабинет сайта.
|
||||||
* `POST /api/web/profile/skin` — Загрузка скина (принимает PNG, считает его SHA-1, сохраняет в `/files/` и привязывает к юзеру).
|
* `POST /api/web/profile/skin` — Загрузка скина (принимает PNG, считает его SHA-1, сохраняет в `/files/` и привязывает к юзеру).
|
||||||
@@ -164,9 +173,11 @@ CREATE TABLE launcher_releases (
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 5.2. Автоматическое добавление сервера в игру (servers.dat)
|
### 5.2. Автоматическое добавление сервера в игру (servers.dat)
|
||||||
|
|
||||||
Перед запуском игры лаунчер читает блок `server_info` из манифеста. Если в `instances/<slug>/servers.dat` отсутствует сервер с IP `server_info.ip`, лаунчер парсит этот NBT-файл и добавляет в него новую запись:
|
Перед запуском игры лаунчер читает блок `server_info` из манифеста. Если в `instances/<slug>/servers.dat` отсутствует сервер с IP `server_info.ip`, лаунчер парсит этот NBT-файл и добавляет в него новую запись:
|
||||||
* **Имя:** `server_info.name`
|
* **Имя:** `server_info.name`
|
||||||
* **IP:** `server_info.ip`
|
* **IP:** `server_info.ip`
|
||||||
|
|
||||||
Это избавляет игроков от ручного ввода IP в клиенте.
|
Это избавляет игроков от ручного ввода IP в клиенте.
|
||||||
|
|
||||||
### 5.3. Пайплайн CI/CD Релиза Лаунчера
|
### 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/`)**: Настраивается отдавать файлы с вечным кэшированием, так как они никогда не меняются (меняется только сам хэш в манифесте).
|
### 6.1. Сервисы
|
||||||
* **Скины (`/skins/`)**: Кэширование настраивается на меньший срок (например, 1 час), чтобы при смене скина игроки видели изменения без жесткого сброса кэша.
|
|
||||||
* **API (`/api/`, `/authserver/`)**: Все запросы перенаправляются на локальный порт Go-приложения (например, `127.0.0.1:8080`).
|
#### `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) — при утере их можно восстановить из манифестов модпаков, но займёт время.
|
||||||
|
|||||||
462
docs/state.md
Normal file
462
docs/state.md
Normal file
@@ -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)
|
||||||
|
|
||||||
|
Путь хранения: `<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 (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=<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
|
||||||
|
```
|
||||||
2
launcher
2
launcher
Submodule launcher updated: 320f009658...e927fff02f
2
server
2
server
Submodule server updated: 551c75a232...7a8e79123f
Reference in New Issue
Block a user