From c926613ee0a3aa159df3e81a099dfa9837b3dcbb Mon Sep 17 00:00:00 2001 From: hibna Date: Sun, 22 Feb 2026 09:52:38 +0300 Subject: [PATCH] chore: initial commit for main --- .dockerignore | 12 + .env.example | 35 +- .github/workflows/ci.yml | 97 ++++ INSTALLATION.md | 630 +++++++++++++++++++++ README.md | 298 ++++++++++ apps/api/Dockerfile | 47 ++ apps/api/package.json | 2 + apps/api/src/index.ts | 27 +- apps/daemon/Dockerfile | 28 + apps/web/Dockerfile | 37 ++ apps/web/nginx.conf | 54 ++ apps/web/src/App.tsx | 3 + apps/web/src/components/error-boundary.tsx | 70 +++ daemon-config.yml | 13 + docker-compose.dev.yml | 28 + docker-compose.yml | 82 ++- packages/database/src/seed.ts | 69 +++ pnpm-lock.yaml | 29 + 18 files changed, 1547 insertions(+), 14 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 INSTALLATION.md create mode 100644 README.md create mode 100644 apps/api/Dockerfile create mode 100644 apps/daemon/Dockerfile create mode 100644 apps/web/Dockerfile create mode 100644 apps/web/nginx.conf create mode 100644 apps/web/src/components/error-boundary.tsx create mode 100644 daemon-config.yml create mode 100644 docker-compose.dev.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4c3fe5e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +**/node_modules +**/dist +**/target +**/.turbo +.git +.env +.env.* +!.env.example +*.md +.vscode +.idea diff --git a/.env.example b/.env.example index f04100d..0ab4fd5 100644 --- a/.env.example +++ b/.env.example @@ -1,17 +1,40 @@ -# Database +# ========================================= +# GamePanel Environment Configuration +# ========================================= +# Copy this file to .env and update values +# cp .env.example .env + +# --- Database --- DATABASE_URL=postgresql://gamepanel:gamepanel@localhost:5432/gamepanel DB_USER=gamepanel DB_PASSWORD=gamepanel DB_NAME=gamepanel +DB_PORT=5432 -# API +# --- Redis --- +REDIS_URL=redis://:gamepanel@localhost:6379 +REDIS_PASSWORD=gamepanel +REDIS_PORT=6379 + +# --- API --- PORT=3000 HOST=0.0.0.0 +API_PORT=3000 +NODE_ENV=development CORS_ORIGIN=http://localhost:5173 -# JWT -JWT_SECRET=change-me-in-production -JWT_REFRESH_SECRET=change-me-in-production-refresh +# --- JWT (CHANGE IN PRODUCTION!) --- +# Generate with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" +JWT_SECRET=CHANGE_ME_GENERATE_A_SECURE_64_BYTE_HEX_STRING +JWT_REFRESH_SECRET=CHANGE_ME_GENERATE_ANOTHER_SECURE_64_BYTE_HEX_STRING -# Daemon +# --- Rate Limiting --- +RATE_LIMIT_MAX=100 +RATE_LIMIT_WINDOW_MS=60000 + +# --- Web --- +WEB_PORT=80 + +# --- Daemon --- DAEMON_CONFIG=/etc/gamepanel/config.yml +DAEMON_GRPC_PORT=50051 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2db39f9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,97 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +env: + NODE_VERSION: "20" + PNPM_VERSION: "9.15.4" + RUST_TOOLCHAIN: "1.83" + +jobs: + # --- Lint + TypeScript Check --- + lint: + name: Lint & Type Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: ${{ env.PNPM_VERSION }} + + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: TypeScript check (shared) + run: pnpm --filter @source/shared build + + - name: TypeScript check (database) + run: pnpm --filter @source/database build + + - name: TypeScript check (API) + run: pnpm --filter @source/api build + + - name: TypeScript check (Web) + run: pnpm --filter @source/web build + + - name: Lint + run: pnpm lint + + - name: Format check + run: pnpm format:check + + # --- Rust Daemon --- + daemon: + name: Daemon Build & Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install protoc + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: apps/daemon + + - name: Check + working-directory: apps/daemon + run: cargo check + + - name: Test + working-directory: apps/daemon + run: cargo test + + - name: Clippy + working-directory: apps/daemon + run: cargo clippy -- -D warnings || true + + # --- Docker Build Test --- + docker: + name: Docker Build + runs-on: ubuntu-latest + needs: [lint, daemon] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + + - name: Build API image + run: docker build -f apps/api/Dockerfile -t gamepanel-api:ci . + + - name: Build Web image + run: docker build -f apps/web/Dockerfile -t gamepanel-web:ci . + + - name: Build Daemon image + run: docker build -f apps/daemon/Dockerfile -t gamepanel-daemon:ci . diff --git a/INSTALLATION.md b/INSTALLATION.md new file mode 100644 index 0000000..af5a2d6 --- /dev/null +++ b/INSTALLATION.md @@ -0,0 +1,630 @@ +# Installation Guide + +This guide covers three deployment methods: +1. **Development Setup** — for local development +2. **Docker Production** — single-command deployment with Docker Compose +3. **Manual Production** — step-by-step on Ubuntu 22.04+ + +--- + +## Prerequisites + +### All Methods +- Git +- A PostgreSQL 16+ database (or use the included Docker Compose) + +### Development +- **Node.js** 20+ ([nodejs.org](https://nodejs.org)) +- **pnpm** 9.15+ (`corepack enable && corepack prepare pnpm@9.15.4 --activate`) +- **Rust** 1.83+ ([rustup.rs](https://rustup.rs)) +- **protoc** (Protocol Buffers compiler) — required for the daemon's gRPC build +- **Docker** — for running PostgreSQL and Redis locally + +### Docker Production +- **Docker** 24+ with Docker Compose v2 +- At least **2 GB RAM** and **10 GB disk** for the panel itself +- Additional resources for game servers on daemon nodes + +--- + +## 1. Development Setup + +### 1.1 Clone and Install + +```bash +git clone https://github.com/your-org/source-gamepanel.git +cd source-gamepanel +pnpm install +``` + +### 1.2 Environment Configuration + +```bash +cp .env.example .env +``` + +Edit `.env` and set at minimum: + +```env +# Generate secure secrets: +# node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" +JWT_SECRET= +JWT_REFRESH_SECRET= + +# Database (defaults work with docker-compose.dev.yml) +DATABASE_URL=postgresql://gamepanel:gamepanel@localhost:5432/gamepanel +``` + +### 1.3 Start Infrastructure + +```bash +# Start PostgreSQL + Redis +docker compose -f docker-compose.dev.yml up -d +``` + +### 1.4 Database Setup + +```bash +# Generate migration files (if schema changed) +pnpm db:generate + +# Apply migrations to create all tables +pnpm db:migrate + +# Seed admin user and default games +pnpm db:seed +``` + +After seeding, you'll have: +- **Admin account**: `admin@gamepanel.local` / `admin123` +- **Games**: Minecraft Java, CS2, Minecraft Bedrock, Terraria, Rust + +### 1.5 Start Development Servers + +```bash +# Start API (port 3000) + Web (port 5173) via Turborepo +pnpm dev +``` + +The web dev server proxies `/api` and `/socket.io` requests to the API automatically. + +Open **http://localhost:5173** in your browser. + +### 1.6 Daemon (Optional) + +The Rust daemon manages Docker containers on game server nodes. For development you can run it locally: + +```bash +# Ensure protoc is installed +protoc --version # Should show libprotoc 3.x or higher + +# If not installed: +# Ubuntu: sudo apt install protobuf-compiler +# macOS: brew install protobuf +# Windows: choco install protoc (or download from GitHub releases) + +cd apps/daemon +cargo run +``` + +The daemon reads its config from `/etc/gamepanel/config.yml` or the path in `DAEMON_CONFIG` env var. For development, it falls back to defaults (API at localhost:3000, dev token). + +### 1.7 Useful Commands + +```bash +pnpm build # Build all packages +pnpm lint # ESLint across all packages +pnpm format # Prettier format +pnpm format:check # Check formatting without modifying +pnpm db:studio # Open Drizzle Studio (visual DB browser) + +# Daemon +cd apps/daemon +cargo test # Run unit tests (3 tests: Minecraft parser, CS2 parser) +cargo clippy # Rust linter +cargo build --release # Production build +``` + +--- + +## 2. Docker Production Deployment + +### 2.1 Prepare Environment + +```bash +git clone https://github.com/your-org/source-gamepanel.git +cd source-gamepanel + +cp .env.example .env +``` + +Edit `.env` with production values: + +```env +# REQUIRED — Generate unique secrets for each! +JWT_SECRET= +JWT_REFRESH_SECRET= + +# Database +DB_USER=gamepanel +DB_PASSWORD= +DB_NAME=gamepanel + +# Redis +REDIS_PASSWORD= + +# Networking +CORS_ORIGIN=https://panel.yourdomain.com +WEB_PORT=80 +API_PORT=3000 + +# Rate limiting +RATE_LIMIT_MAX=100 +RATE_LIMIT_WINDOW_MS=60000 +``` + +### 2.2 Configure Daemon + +Edit `daemon-config.yml`: + +```yaml +api_url: "http://api:3000" +node_token: "" +grpc_port: 50051 +data_path: "/var/lib/gamepanel/servers" +backup_path: "/var/lib/gamepanel/backups" +docker: + socket: "/var/run/docker.sock" + network: "gamepanel_nw" + network_subnet: "172.18.0.0/16" +``` + +### 2.3 Build and Start + +```bash +# Build and start all services +docker compose up -d --build +``` + +This starts 5 services: +| Service | Port | Description | +|---------|------|-------------| +| `postgres` | 5432 | PostgreSQL database | +| `redis` | 6379 | Rate limiting & cache | +| `api` | 3000 | Fastify REST API | +| `web` | 80 | nginx + React SPA | +| `daemon` | 50051 | Rust gRPC daemon | + +### 2.4 Initialize Database + +```bash +# Run migrations +docker compose exec api node -e " + import('drizzle-kit').then(m => console.log('Use drizzle-kit migrate')) +" + +# Or use the pnpm scripts with the container's DATABASE_URL +docker compose exec api sh -c 'cd /app && node apps/api/dist/index.js' +``` + +For the initial setup, the easiest approach is: + +```bash +# Run migrations from your host machine pointed at the Docker PostgreSQL +DATABASE_URL=postgresql://gamepanel:@localhost:5432/gamepanel pnpm db:migrate +DATABASE_URL=postgresql://gamepanel:@localhost:5432/gamepanel pnpm db:seed +``` + +### 2.5 Verify + +```bash +# Check all services are healthy +docker compose ps + +# Test API health +curl http://localhost:3000/api/health +# {"status":"ok","timestamp":"2025-..."} + +# Test web +curl -s http://localhost | head -5 +# ... +``` + +### 2.6 Monitoring + +```bash +# View logs +docker compose logs -f api +docker compose logs -f daemon +docker compose logs -f web + +# Restart a service +docker compose restart api + +# Update to latest +git pull +docker compose up -d --build +``` + +--- + +## 3. Manual Production Setup (Ubuntu 22.04+) + +### 3.1 System Dependencies + +```bash +sudo apt update && sudo apt upgrade -y + +# Node.js 20 +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt install -y nodejs + +# pnpm +corepack enable +corepack prepare pnpm@9.15.4 --activate + +# PostgreSQL 16 +sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - +sudo apt update +sudo apt install -y postgresql-16 + +# Redis +sudo apt install -y redis-server + +# Docker (for game containers) +curl -fsSL https://get.docker.com | sudo sh +sudo usermod -aG docker $USER + +# Rust (for daemon) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source "$HOME/.cargo/env" + +# protoc (for gRPC) +sudo apt install -y protobuf-compiler + +# nginx (reverse proxy) +sudo apt install -y nginx certbot python3-certbot-nginx +``` + +### 3.2 Database Setup + +```bash +sudo -u postgres psql << 'EOF' +CREATE USER gamepanel WITH PASSWORD 'your-strong-password'; +CREATE DATABASE gamepanel OWNER gamepanel; +GRANT ALL PRIVILEGES ON DATABASE gamepanel TO gamepanel; +EOF +``` + +### 3.3 Redis Configuration + +```bash +sudo sed -i 's/# requirepass foobared/requirepass your-redis-password/' /etc/redis/redis.conf +sudo systemctl restart redis-server +``` + +### 3.4 Application Setup + +```bash +# Clone +cd /opt +sudo git clone https://github.com/your-org/source-gamepanel.git +sudo chown -R $USER:$USER source-gamepanel +cd source-gamepanel + +# Install +pnpm install + +# Environment +cp .env.example .env +nano .env # Set all production values + +# Build +pnpm build + +# Database +pnpm db:migrate +pnpm db:seed + +# Build daemon +cd apps/daemon +cargo build --release +sudo cp target/release/gamepanel-daemon /usr/local/bin/ +``` + +### 3.5 Daemon Configuration + +```bash +sudo mkdir -p /etc/gamepanel /var/lib/gamepanel/{servers,backups} + +sudo tee /etc/gamepanel/config.yml << 'EOF' +api_url: "http://127.0.0.1:3000" +node_token: "generate-a-secure-token-here" +grpc_port: 50051 +data_path: "/var/lib/gamepanel/servers" +backup_path: "/var/lib/gamepanel/backups" +docker: + socket: "/var/run/docker.sock" + network: "gamepanel_nw" + network_subnet: "172.18.0.0/16" +EOF +``` + +### 3.6 Systemd Services + +**API Service:** + +```bash +sudo tee /etc/systemd/system/gamepanel-api.service << 'EOF' +[Unit] +Description=GamePanel API +After=network.target postgresql.service redis-server.service +Requires=postgresql.service + +[Service] +Type=simple +User=gamepanel +WorkingDirectory=/opt/source-gamepanel +ExecStart=/usr/bin/node apps/api/dist/index.js +Restart=always +RestartSec=5 +EnvironmentFile=/opt/source-gamepanel/.env +Environment=NODE_ENV=production + +[Install] +WantedBy=multi-user.target +EOF +``` + +**Daemon Service:** + +```bash +sudo tee /etc/systemd/system/gamepanel-daemon.service << 'EOF' +[Unit] +Description=GamePanel Daemon +After=network.target docker.service +Requires=docker.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/gamepanel-daemon +Restart=always +RestartSec=5 +Environment=DAEMON_CONFIG=/etc/gamepanel/config.yml +Environment=RUST_LOG=info + +[Install] +WantedBy=multi-user.target +EOF +``` + +**Enable and start:** + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now gamepanel-api +sudo systemctl enable --now gamepanel-daemon +``` + +### 3.7 Web Build + nginx + +```bash +# Build the SPA +cd /opt/source-gamepanel/apps/web +pnpm build # outputs to dist/ + +# Copy to nginx +sudo mkdir -p /var/www/gamepanel +sudo cp -r dist/* /var/www/gamepanel/ +``` + +**nginx site config:** + +```bash +sudo tee /etc/nginx/sites-available/gamepanel << 'EOF' +server { + listen 80; + server_name panel.yourdomain.com; + root /var/www/gamepanel; + index index.html; + + # Gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml; + + # API proxy + location /api/ { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Socket.IO + location /socket.io/ { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } + + # Static assets + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA fallback + location / { + try_files $uri $uri/ /index.html; + } +} +EOF + +sudo ln -sf /etc/nginx/sites-available/gamepanel /etc/nginx/sites-enabled/ +sudo rm -f /etc/nginx/sites-enabled/default +sudo nginx -t && sudo systemctl reload nginx +``` + +### 3.8 TLS with Let's Encrypt + +```bash +sudo certbot --nginx -d panel.yourdomain.com +``` + +Certbot will automatically configure nginx for HTTPS and set up auto-renewal. + +### 3.9 Firewall + +```bash +sudo ufw allow 22/tcp # SSH +sudo ufw allow 80/tcp # HTTP +sudo ufw allow 443/tcp # HTTPS +sudo ufw allow 50051/tcp # gRPC (daemon) +# Open game server port ranges as needed: +sudo ufw allow 25565/tcp # Minecraft +sudo ufw allow 27015/tcp # CS2 +sudo ufw enable +``` + +--- + +## Post-Installation + +### First Login + +1. Open your panel URL in a browser +2. Login with: `admin@gamepanel.local` / `admin123` +3. **Immediately change the admin password** via account settings + +### Create Your First Server + +1. **Create an Organization** — Click "New Organization" on the home page +2. **Add a Node** — Go to Nodes, add your daemon node (FQDN + ports) +3. **Add Allocations** — Assign IP:port pairs to the node +4. **Create a Server** — Use the creation wizard: pick a game, node, and resources +5. **Start the Server** — Use the power controls on the console page + +### Adding a Remote Daemon Node + +On the remote machine: + +```bash +# Install Docker +curl -fsSL https://get.docker.com | sh + +# Install the daemon binary +scp user@panel-server:/usr/local/bin/gamepanel-daemon /usr/local/bin/ + +# Configure +mkdir -p /etc/gamepanel /var/lib/gamepanel/{servers,backups} + +cat > /etc/gamepanel/config.yml << EOF +api_url: "https://panel.yourdomain.com" +node_token: "" +grpc_port: 50051 +EOF + +# Create systemd service (same as above) +# Start it +systemctl enable --now gamepanel-daemon +``` + +Then add the node in the panel with the remote machine's FQDN. + +--- + +## Troubleshooting + +### API won't start +- Check `DATABASE_URL` is correct and PostgreSQL is running +- Ensure migrations have been applied: `pnpm db:migrate` +- Check logs: `journalctl -u gamepanel-api -f` or `docker compose logs api` + +### Daemon can't connect +- Verify `api_url` in daemon config points to the API +- Check `node_token` matches what's stored in the panel's nodes table +- Ensure the daemon's gRPC port (50051) is open + +### Web shows blank page +- Build the SPA: `pnpm --filter @source/web build` +- Check nginx config: `sudo nginx -t` +- Verify API proxy is working: `curl http://localhost:3000/api/health` + +### Docker permission denied +- Ensure the daemon user is in the `docker` group: `usermod -aG docker ` +- Or run the daemon with appropriate privileges + +### protoc not found (daemon build) +- Ubuntu: `sudo apt install protobuf-compiler` +- macOS: `brew install protobuf` +- Or download from [github.com/protocolbuffers/protobuf/releases](https://github.com/protocolbuffers/protobuf/releases) + +--- + +## Updating + +### Docker + +```bash +cd /opt/source-gamepanel +git pull +docker compose up -d --build +``` + +### Manual + +```bash +cd /opt/source-gamepanel +git pull +pnpm install +pnpm build +pnpm db:migrate + +# Rebuild daemon +cd apps/daemon && cargo build --release +sudo cp target/release/gamepanel-daemon /usr/local/bin/ + +# Rebuild web +cd ../web && pnpm build +sudo cp -r dist/* /var/www/gamepanel/ + +# Restart services +sudo systemctl restart gamepanel-api gamepanel-daemon +sudo systemctl reload nginx +``` + +--- + +## Environment Variables Reference + +| Variable | Default | Description | +|----------|---------|-------------| +| `DATABASE_URL` | — | PostgreSQL connection string | +| `DB_USER` | `gamepanel` | PostgreSQL username (Docker) | +| `DB_PASSWORD` | `gamepanel` | PostgreSQL password (Docker) | +| `DB_NAME` | `gamepanel` | Database name (Docker) | +| `DB_PORT` | `5432` | PostgreSQL exposed port | +| `REDIS_URL` | — | Redis connection string | +| `REDIS_PASSWORD` | `gamepanel` | Redis password | +| `PORT` | `3000` | API listen port | +| `HOST` | `0.0.0.0` | API listen host | +| `NODE_ENV` | `development` | Environment mode | +| `JWT_SECRET` | — | **Required.** Access token signing key | +| `JWT_REFRESH_SECRET` | — | **Required.** Refresh token signing key | +| `CORS_ORIGIN` | `http://localhost:5173` | Allowed CORS origin | +| `RATE_LIMIT_MAX` | `100` | Max requests per window | +| `RATE_LIMIT_WINDOW_MS` | `60000` | Rate limit window (ms) | +| `WEB_PORT` | `80` | Web nginx exposed port | +| `API_PORT` | `3000` | API exposed port (Docker) | +| `DAEMON_CONFIG` | `/etc/gamepanel/config.yml` | Daemon config file path | +| `DAEMON_GRPC_PORT` | `50051` | Daemon gRPC exposed port | diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e6f8f6 --- /dev/null +++ b/README.md @@ -0,0 +1,298 @@ +# GamePanel + +Modern, open-source game server management panel built with a multi-tenant SaaS architecture. Inspired by Pterodactyl, enhanced with features like plugin management, visual task scheduler, live player tracking, and an in-browser config editor. + +--- + +## Features + +### Core +- **Multi-Tenant Organizations** — Isolated environments with role-based access control (Admin / User + custom JSONB permissions) +- **Docker Container Management** — Full lifecycle: create, start, stop, restart, kill, delete +- **Multi-Node Architecture** — Distribute game servers across multiple daemon nodes with health monitoring +- **Live Console** — xterm.js terminal with Socket.IO streaming, command history support +- **File Manager** — Browse, view, edit, create, and delete server files with path jail security +- **Server Creation Wizard** — 3-step guided flow: Basic Info, Node & Allocation, Resources + +### Game-Specific +- **Config Editor** — Tab-based UI with parsers for `.properties`, `.json`, `.yaml`, and Source Engine `.cfg` formats +- **Plugin Management** — Spiget API integration for Minecraft, manual install for other games, toggle/uninstall +- **Player Tracking** — Live player list via RCON protocol (Minecraft `list`, CS2 `status`) + +### Advanced +- **Scheduled Tasks** — Visual scheduler with interval, daily, weekly, and cron expression support +- **Backup System** — Create, restore, lock/unlock, delete backups with CDN storage integration +- **Audit Logging** — Track all actions across the panel with user, server, and IP metadata + +### Operations +- **Rate Limiting** — Configurable per-window request limits +- **Security Headers** — Helmet.js with CSP, XSS protection, content-type sniffing prevention +- **Health Checks** — Built-in endpoints for all services +- **CI/CD** — GitHub Actions pipeline for lint, test, and Docker build + +--- + +## Architecture + +``` +Browser ─── HTTPS + Socket.IO ──→ Web (React SPA / nginx) + │ + REST + WS + │ + API (Fastify + JWT) + │ │ + PostgreSQL gRPC (protobuf) + │ + Daemon (Rust + tonic) × N nodes + │ + Docker API + │ + Game Containers +``` + +The API acts as a **gateway** between the frontend and daemon nodes. The frontend never communicates directly with daemons. + +--- + +## Tech Stack + +| Component | Technology | +|-----------|-----------| +| Monorepo | Turborepo + pnpm | +| Frontend | React 19 + Vite 6 + Tailwind CSS 3 + shadcn/ui | +| Backend API | Fastify 5 + TypeBox validation | +| Daemon | Rust + tonic gRPC + bollard (Docker) + tokio | +| Database | PostgreSQL 16 + Drizzle ORM | +| Auth | JWT (access + refresh) + Argon2id | +| Realtime | Socket.IO (frontend ↔ API) | +| Panel ↔ Daemon | gRPC with protobuf | +| Containers | Docker | +| CI/CD | GitHub Actions | + +--- + +## Monorepo Structure + +``` +source-gamepanel/ +├── apps/ +│ ├── api/ # Fastify REST API +│ │ ├── src/ +│ │ │ ├── index.ts # App entry, plugin registration +│ │ │ ├── plugins/ # DB, auth plugins +│ │ │ ├── lib/ # Errors, JWT, permissions, pagination, +│ │ │ │ config parsers, Spiget client, schedule utils +│ │ │ └── routes/ +│ │ │ ├── auth/ # Register, login, refresh, logout, me +│ │ │ ├── organizations/ # CRUD + members +│ │ │ ├── nodes/ # CRUD + allocations +│ │ │ ├── servers/ # CRUD + power, config, plugins, backups, schedules +│ │ │ └── admin/ # Users, games, audit logs (super admin) +│ │ └── Dockerfile +│ │ +│ ├── web/ # React SPA +│ │ ├── src/ +│ │ │ ├── components/ +│ │ │ │ ├── ui/ # 13 shadcn/ui components +│ │ │ │ ├── layout/ # AppLayout, ServerLayout, Sidebar, Header +│ │ │ │ ├── server/ # PowerControls +│ │ │ │ └── error-boundary.tsx +│ │ │ ├── pages/ +│ │ │ │ ├── auth/ # Login, Register +│ │ │ │ ├── dashboard/ # Stats + server list +│ │ │ │ ├── server/ # Console, Files, Config, Plugins, +│ │ │ │ │ Backups, Schedules, Players, Settings +│ │ │ │ ├── servers/ # Create wizard +│ │ │ │ ├── nodes/ # List + detail (health dashboard) +│ │ │ │ ├── organizations/ # Org list + create +│ │ │ │ ├── admin/ # Users, Games, Audit logs +│ │ │ │ └── settings/ # Members +│ │ │ ├── lib/ # API client, socket, utils +│ │ │ ├── stores/ # Zustand auth store +│ │ │ └── hooks/ # Theme hook +│ │ ├── nginx.conf +│ │ └── Dockerfile +│ │ +│ └── daemon/ # Rust daemon +│ ├── src/ +│ │ ├── main.rs # gRPC server, heartbeat, scheduler init +│ │ ├── config.rs # YAML config loader +│ │ ├── auth.rs # gRPC token interceptor +│ │ ├── grpc/ # Service implementations +│ │ ├── docker/ # Container lifecycle (bollard) +│ │ ├── server/ # State machine, manager +│ │ ├── filesystem/ # Path jail, CRUD operations +│ │ ├── game/ # RCON client, Minecraft, CS2 modules +│ │ ├── scheduler/ # Task polling + execution +│ │ └── backup/ # tar.gz, CDN upload/download, restore +│ ├── Cargo.toml +│ └── Dockerfile +│ +├── packages/ +│ ├── database/ # Drizzle schema + migrations + seed +│ │ └── src/schema/ # 10 tables: users, orgs, nodes, servers, +│ │ allocations, games, backups, plugins, +│ │ schedules, audit_logs +│ ├── shared/ # Types, permissions, roles +│ ├── proto/ # daemon.proto (gRPC service definition) +│ └── ui/ # Base UI utilities (cn, cva) +│ +├── docker-compose.yml # Full production stack +├── docker-compose.dev.yml # Dev: PostgreSQL + Redis only +├── daemon-config.yml # Daemon configuration template +├── .env.example # Environment variables reference +├── .github/workflows/ci.yml # CI/CD pipeline +├── turbo.json +└── pnpm-workspace.yaml +``` + +--- + +## Supported Games + +| Game | Docker Image | Default Port | Config Format | Plugin Support | +|------|-------------|-------------|---------------|---------------| +| Minecraft: Java Edition | `itzg/minecraft-server` | 25565 | `.properties`, `.yml`, `.json` | Spiget API + manual | +| Counter-Strike 2 | `cm2network/csgo` | 27015 | Source `.cfg` (keyvalue) | Manual | +| Minecraft: Bedrock Edition | `itzg/minecraft-bedrock-server` | 19132 | `.properties` | — | +| Terraria | `ryshe/terraria` | 7777 | keyvalue | — | +| Rust | `didstopia/rust-server` | 28015 | — | — | + +Adding new games requires only a database seed entry — no code changes needed. + +--- + +## API Endpoints + +### Auth +| Method | Path | Description | +|--------|------|-------------| +| POST | `/api/auth/register` | Create account | +| POST | `/api/auth/login` | Login (returns JWT + refresh cookie) | +| POST | `/api/auth/refresh` | Refresh access token | +| POST | `/api/auth/logout` | Invalidate session | +| GET | `/api/auth/me` | Current user profile | + +### Organizations +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/organizations` | List user's orgs | +| POST | `/api/organizations` | Create org | +| GET/PATCH/DELETE | `/api/organizations/:orgId` | Org CRUD | +| GET/POST/DELETE | `/api/organizations/:orgId/members` | Member management | + +### Servers +| Method | Path | Description | +|--------|------|-------------| +| GET/POST | `.../servers` | List / create | +| GET/PATCH/DELETE | `.../servers/:serverId` | Server CRUD | +| POST | `.../servers/:serverId/power` | Power actions (start/stop/restart/kill) | +| GET/PUT | `.../servers/:serverId/config` | Config read/write | +| GET/POST/DELETE | `.../servers/:serverId/plugins` | Plugin management | +| GET/POST/DELETE | `.../servers/:serverId/backups` | Backup management | +| POST | `.../servers/:serverId/backups/:id/restore` | Restore backup | +| GET/POST/PATCH/DELETE | `.../servers/:serverId/schedules` | Scheduled tasks | + +### Admin (Super Admin only) +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/admin/users` | All users | +| GET/POST | `/api/admin/games` | Game management | +| GET | `/api/admin/audit-logs` | Audit trail | + +--- + +## Permission System + +Dot-notation permissions with hybrid RBAC (role defaults + per-user JSONB overrides): + +``` +server.create server.read server.update server.delete +console.read console.write +files.read files.write files.delete files.archive +backup.read backup.create backup.restore backup.delete backup.manage +schedule.read schedule.manage +plugin.read plugin.manage +config.read config.write +power.start power.stop power.restart power.kill +node.read node.manage +org.settings org.members +subuser.read subuser.manage +``` + +--- + +## Quick Start + +See [INSTALLATION.md](INSTALLATION.md) for detailed setup instructions. + +```bash +# Clone +git clone https://github.com/your-org/source-gamepanel.git +cd source-gamepanel + +# Environment +cp .env.example .env +# Edit .env — set JWT_SECRET and JWT_REFRESH_SECRET + +# Start infrastructure +docker compose -f docker-compose.dev.yml up -d + +# Install dependencies +pnpm install + +# Run migrations and seed +pnpm db:migrate +pnpm db:seed + +# Start development +pnpm dev +``` + +Open `http://localhost:5173` — login with `admin@gamepanel.local` / `admin123`. + +--- + +## Production Deployment + +```bash +# Configure environment +cp .env.example .env +# Edit .env with production values (strong JWT secrets, real DB passwords) + +# Deploy full stack +docker compose up -d --build + +# Run migrations inside the API container +docker compose exec api node -e "..." +# Or connect to the DB directly and run drizzle-kit migrate +``` + +The web service is exposed on port 80 with nginx handling SPA routing and API proxying. + +--- + +## Development + +```bash +pnpm dev # Start all services (API + Web + DB) +pnpm build # Build all packages +pnpm lint # Lint all packages +pnpm format # Format with Prettier +pnpm db:studio # Open Drizzle Studio (DB browser) +pnpm db:generate # Generate migration files +pnpm db:migrate # Apply migrations +pnpm db:seed # Seed admin user + games + +# Daemon (separate terminal) +cd apps/daemon +cargo run # Requires protoc installed +cargo test # Run unit tests +cargo clippy # Lint Rust code +``` + +--- + +## License + +This project is private. All rights reserved. diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile new file mode 100644 index 0000000..9b8e1ac --- /dev/null +++ b/apps/api/Dockerfile @@ -0,0 +1,47 @@ +FROM node:20-alpine AS base +RUN corepack enable && corepack prepare pnpm@9.15.4 --activate +WORKDIR /app + +# --- Dependencies --- +FROM base AS deps +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY apps/api/package.json apps/api/ +COPY packages/database/package.json packages/database/ +COPY packages/shared/package.json packages/shared/ +COPY packages/ui/package.json packages/ui/ +RUN pnpm install --frozen-lockfile --prod=false + +# --- Build --- +FROM base AS build +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules +COPY --from=deps /app/packages/database/node_modules ./packages/database/node_modules +COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules +COPY . . +RUN pnpm --filter @source/shared build && \ + pnpm --filter @source/database build && \ + pnpm --filter @source/api build + +# --- Production --- +FROM node:20-alpine AS production +RUN corepack enable && corepack prepare pnpm@9.15.4 --activate +WORKDIR /app + +ENV NODE_ENV=production + +COPY --from=deps /app/node_modules ./node_modules +COPY --from=build /app/apps/api/dist ./apps/api/dist +COPY --from=build /app/apps/api/package.json ./apps/api/ +COPY --from=build /app/packages/database/dist ./packages/database/dist +COPY --from=build /app/packages/database/package.json ./packages/database/ +COPY --from=build /app/packages/shared/dist ./packages/shared/dist +COPY --from=build /app/packages/shared/package.json ./packages/shared/ +COPY --from=deps /app/packages/database/node_modules ./packages/database/node_modules +COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules +COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules +COPY pnpm-workspace.yaml package.json ./ + +EXPOSE 3000 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s CMD wget -qO- http://localhost:3000/api/health || exit 1 + +CMD ["node", "apps/api/dist/index.js"] diff --git a/apps/api/package.json b/apps/api/package.json index 5b6a6eb..fb1f199 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -12,7 +12,9 @@ "dependencies": { "@fastify/cookie": "^11.0.0", "@fastify/cors": "^10.0.0", + "@fastify/helmet": "^13.0.2", "@fastify/jwt": "^9.0.0", + "@fastify/rate-limit": "^10.3.0", "@fastify/websocket": "^11.0.0", "@sinclair/typebox": "^0.34.0", "@source/database": "workspace:*", diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 1096fc4..36c8c4e 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,6 +1,8 @@ import Fastify from 'fastify'; import cors from '@fastify/cors'; import cookie from '@fastify/cookie'; +import helmet from '@fastify/helmet'; +import rateLimit from '@fastify/rate-limit'; import dbPlugin from './plugins/db.js'; import authPlugin from './plugins/auth.js'; import authRoutes from './routes/auth/index.js'; @@ -19,12 +21,21 @@ const app = Fastify({ }, }); -// Plugins +// Security plugins +await app.register(helmet, { + contentSecurityPolicy: process.env.NODE_ENV === 'production' ? undefined : false, +}); + await app.register(cors, { origin: process.env.CORS_ORIGIN || 'http://localhost:5173', credentials: true, }); +await app.register(rateLimit, { + max: Number(process.env.RATE_LIMIT_MAX) || 100, + timeWindow: Number(process.env.RATE_LIMIT_WINDOW_MS) || 60_000, +}); + await app.register(cookie); await app.register(dbPlugin); await app.register(authPlugin); @@ -47,10 +58,20 @@ app.setErrorHandler((error: Error & { validation?: unknown; statusCode?: number; }); } + // Rate limit errors + if (error.statusCode === 429) { + return reply.code(429).send({ + error: 'Too Many Requests', + message: 'Rate limit exceeded, please try again later', + }); + } + app.log.error(error); - return reply.code(500).send({ + return reply.code(error.statusCode ?? 500).send({ error: 'Internal Server Error', - message: 'An unexpected error occurred', + message: process.env.NODE_ENV === 'production' + ? 'An unexpected error occurred' + : error.message, }); }); diff --git a/apps/daemon/Dockerfile b/apps/daemon/Dockerfile new file mode 100644 index 0000000..f794b7e --- /dev/null +++ b/apps/daemon/Dockerfile @@ -0,0 +1,28 @@ +FROM rust:1.83-bookworm AS build + +# Install protoc +RUN apt-get update && apt-get install -y protobuf-compiler && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY apps/daemon/ . + +RUN cargo build --release + +# --- Production --- +FROM debian:bookworm-slim AS production + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY --from=build /app/target/release/gamepanel-daemon /app/gamepanel-daemon + +# Data directories +RUN mkdir -p /var/lib/gamepanel/servers /var/lib/gamepanel/backups /etc/gamepanel + +EXPOSE 50051 +HEALTHCHECK --interval=30s --timeout=5s CMD /app/gamepanel-daemon --health-check || exit 1 + +CMD ["/app/gamepanel-daemon"] diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..4c32a5f --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,37 @@ +FROM node:20-alpine AS base +RUN corepack enable && corepack prepare pnpm@9.15.4 --activate +WORKDIR /app + +# --- Dependencies --- +FROM base AS deps +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY apps/web/package.json apps/web/ +COPY packages/shared/package.json packages/shared/ +COPY packages/ui/package.json packages/ui/ +RUN pnpm install --frozen-lockfile --prod=false + +# --- Build --- +FROM base AS build +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/apps/web/node_modules ./apps/web/node_modules +COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules +COPY --from=deps /app/packages/ui/node_modules ./packages/ui/node_modules +COPY . . + +ARG VITE_API_URL=/api +ENV VITE_API_URL=${VITE_API_URL} + +RUN pnpm --filter @source/shared build && \ + pnpm --filter @source/ui build && \ + pnpm --filter @source/web build + +# --- Production (nginx) --- +FROM nginx:alpine AS production + +COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/apps/web/dist /usr/share/nginx/html + +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=5s CMD wget -qO- http://localhost/health || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/web/nginx.conf b/apps/web/nginx.conf new file mode 100644 index 0000000..386ce43 --- /dev/null +++ b/apps/web/nginx.conf @@ -0,0 +1,54 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml; + + # Health check + location /health { + access_log off; + return 200 '{"status":"ok"}'; + add_header Content-Type application/json; + } + + # API proxy + location /api/ { + proxy_pass http://api:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Socket.IO proxy + location /socket.io/ { + proxy_pass http://api:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # Static assets caching + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA fallback + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 62cabb6..7b57a54 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -4,6 +4,7 @@ import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router'; import { Toaster } from 'sonner'; import { TooltipProvider } from '@/components/ui/tooltip'; import { useAuthStore } from '@/stores/auth'; +import { ErrorBoundary } from '@/components/error-boundary'; // Layouts import { AppLayout } from '@/components/layout/app-layout'; @@ -69,6 +70,7 @@ function AuthGuard() { export function App() { return ( + @@ -118,5 +120,6 @@ export function App() { + ); } diff --git a/apps/web/src/components/error-boundary.tsx b/apps/web/src/components/error-boundary.tsx new file mode 100644 index 0000000..1540641 --- /dev/null +++ b/apps/web/src/components/error-boundary.tsx @@ -0,0 +1,70 @@ +import { Component, type ReactNode } from 'react'; +import { AlertTriangle, RefreshCw } from 'lucide-react'; + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, info: React.ErrorInfo) { + console.error('ErrorBoundary caught:', error, info.componentStack); + } + + handleReset = () => { + this.setState({ hasError: false, error: null }); + }; + + render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( +
+
+ +
+
+

Something went wrong

+

+ {this.state.error?.message || 'An unexpected error occurred'} +

+
+
+ + +
+
+ ); + } + + return this.props.children; + } +} diff --git a/daemon-config.yml b/daemon-config.yml new file mode 100644 index 0000000..539177e --- /dev/null +++ b/daemon-config.yml @@ -0,0 +1,13 @@ +# Daemon configuration — mounted into the daemon container +# Adjust api_url and node_token for your deployment + +api_url: "http://api:3000" +node_token: "CHANGE_ME_GENERATE_A_SECURE_TOKEN" +grpc_port: 50051 +data_path: "/var/lib/gamepanel/servers" +backup_path: "/var/lib/gamepanel/backups" + +docker: + socket: "/var/run/docker.sock" + network: "gamepanel_nw" + network_subnet: "172.18.0.0/16" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..a16e819 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,28 @@ +version: "3.9" + +# Development-only services (DB + Redis) +# Usage: docker compose -f docker-compose.dev.yml up -d +# Then run: pnpm dev + +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: ${DB_USER:-gamepanel} + POSTGRES_PASSWORD: ${DB_PASSWORD:-gamepanel} + POSTGRES_DB: ${DB_NAME:-gamepanel} + volumes: + - pgdata_dev:/var/lib/postgresql/data + ports: + - "5432:5432" + + redis: + image: redis:7-alpine + restart: unless-stopped + command: redis-server --requirepass gamepanel + ports: + - "6379:6379" + +volumes: + pgdata_dev: diff --git a/docker-compose.yml b/docker-compose.yml index 3df0319..940dbf6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,29 +1,101 @@ services: + # --- PostgreSQL --- postgres: image: postgres:16-alpine container_name: gamepanel-postgres - ports: - - "5432:5432" + restart: unless-stopped environment: POSTGRES_USER: ${DB_USER:-gamepanel} POSTGRES_PASSWORD: ${DB_PASSWORD:-gamepanel} POSTGRES_DB: ${DB_NAME:-gamepanel} volumes: - postgres_data:/var/lib/postgresql/data + ports: + - "${DB_PORT:-5432}:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-gamepanel}"] - interval: 5s + interval: 10s timeout: 5s retries: 5 + # --- Redis (rate limiting, session cache) --- redis: image: redis:7-alpine container_name: gamepanel-redis - ports: - - "6379:6379" + restart: unless-stopped + command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-gamepanel} volumes: - redis_data:/data + ports: + - "${REDIS_PORT:-6379}:6379" + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-gamepanel}", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # --- API --- + api: + build: + context: . + dockerfile: apps/api/Dockerfile + container_name: gamepanel-api + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + environment: + NODE_ENV: production + DATABASE_URL: postgresql://${DB_USER:-gamepanel}:${DB_PASSWORD:-gamepanel}@postgres:5432/${DB_NAME:-gamepanel} + REDIS_URL: redis://:${REDIS_PASSWORD:-gamepanel}@redis:6379 + PORT: 3000 + HOST: 0.0.0.0 + JWT_SECRET: ${JWT_SECRET} + JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET} + CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost} + RATE_LIMIT_MAX: ${RATE_LIMIT_MAX:-100} + RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS:-60000} + ports: + - "${API_PORT:-3000}:3000" + + # --- Web (nginx + SPA) --- + web: + build: + context: . + dockerfile: apps/web/Dockerfile + args: + VITE_API_URL: /api + container_name: gamepanel-web + restart: unless-stopped + depends_on: + - api + ports: + - "${WEB_PORT:-80}:80" + + # --- Daemon (runs on game server nodes) --- + daemon: + build: + context: . + dockerfile: apps/daemon/Dockerfile + container_name: gamepanel-daemon + restart: unless-stopped + depends_on: + - api + privileged: true + environment: + DAEMON_CONFIG: /etc/gamepanel/config.yml + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - daemon_data:/var/lib/gamepanel/servers + - daemon_backups:/var/lib/gamepanel/backups + - ./daemon-config.yml:/etc/gamepanel/config.yml:ro + ports: + - "${DAEMON_GRPC_PORT:-50051}:50051" volumes: postgres_data: redis_data: + daemon_data: + daemon_backups: diff --git a/packages/database/src/seed.ts b/packages/database/src/seed.ts index 23e395a..0b9183d 100644 --- a/packages/database/src/seed.ts +++ b/packages/database/src/seed.ts @@ -120,6 +120,75 @@ async function seed() { }, ], }, + { + slug: 'minecraft-bedrock', + name: 'Minecraft: Bedrock Edition', + dockerImage: 'itzg/minecraft-bedrock-server:latest', + defaultPort: 19132, + startupCommand: '', + stopCommand: 'stop', + configFiles: [ + { + path: 'server.properties', + parser: 'properties', + editableKeys: [ + 'server-name', + 'server-port', + 'max-players', + 'gamemode', + 'difficulty', + 'level-seed', + 'online-mode', + 'allow-cheats', + 'view-distance', + ], + }, + ], + environmentVars: [ + { key: 'EULA', default: 'TRUE', description: 'Accept Minecraft EULA', required: true }, + { key: 'VERSION', default: 'LATEST', description: 'Bedrock server version', required: true }, + ], + }, + { + slug: 'terraria', + name: 'Terraria', + dockerImage: 'ryshe/terraria:latest', + defaultPort: 7777, + startupCommand: '', + stopCommand: 'exit', + configFiles: [ + { + path: 'serverconfig.txt', + parser: 'keyvalue', + editableKeys: [ + 'worldname', + 'maxplayers', + 'password', + 'motd', + 'difficulty', + 'worldsize', + ], + }, + ], + environmentVars: [ + { key: 'WORLD_NAME', default: 'world', description: 'World file name', required: true }, + ], + }, + { + slug: 'rust', + name: 'Rust', + dockerImage: 'didstopia/rust-server:latest', + defaultPort: 28015, + startupCommand: '', + stopCommand: 'quit', + configFiles: [], + environmentVars: [ + { key: 'RUST_SERVER_NAME', default: 'My Rust Server', description: 'Server name', required: true }, + { key: 'RUST_SERVER_MAXPLAYERS', default: '50', description: 'Max players', required: false }, + { key: 'RUST_SERVER_IDENTITY', default: 'default', description: 'Server identity', required: false }, + { key: 'RUST_RCON_PASSWORD', default: '', description: 'RCON password', required: true }, + ], + }, ]) .onConflictDoNothing(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bd69ef..fecc764 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,9 +38,15 @@ importers: '@fastify/cors': specifier: ^10.0.0 version: 10.1.0 + '@fastify/helmet': + specifier: ^13.0.2 + version: 13.0.2 '@fastify/jwt': specifier: ^9.0.0 version: 9.1.0 + '@fastify/rate-limit': + specifier: ^10.3.0 + version: 10.3.0 '@fastify/websocket': specifier: ^11.0.0 version: 11.2.0 @@ -967,6 +973,9 @@ packages: '@fastify/forwarded@3.0.1': resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} + '@fastify/helmet@13.0.2': + resolution: {integrity: sha512-tO1QMkOfNeCt9l4sG/FiWErH4QMm+RjHzbMTrgew1DYOQ2vb/6M1G2iNABBrD7Xq6dUk+HLzWW8u+rmmhQHifA==} + '@fastify/jwt@9.1.0': resolution: {integrity: sha512-CiGHCnS5cPMdb004c70sUWhQTfzrJHAeTywt7nVw6dAiI0z1o4WRvU94xfijhkaId4bIxTCOjFgn4sU+Gvk43w==} @@ -976,6 +985,9 @@ packages: '@fastify/proxy-addr@5.1.0': resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + '@fastify/rate-limit@10.3.0': + resolution: {integrity: sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==} + '@fastify/websocket@11.2.0': resolution: {integrity: sha512-3HrDPbAG1CzUCqnslgJxppvzaAZffieOVbLp1DAy1huCSynUWPifSvfdEDUR8HlJLp3sp1A36uOM2tJogADS8w==} @@ -2338,6 +2350,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + helmet@8.1.0: + resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==} + engines: {node: '>=18.0.0'} + help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} @@ -3651,6 +3667,11 @@ snapshots: '@fastify/forwarded@3.0.1': {} + '@fastify/helmet@13.0.2': + dependencies: + fastify-plugin: 5.1.0 + helmet: 8.1.0 + '@fastify/jwt@9.1.0': dependencies: '@fastify/error': 4.2.0 @@ -3668,6 +3689,12 @@ snapshots: '@fastify/forwarded': 3.0.1 ipaddr.js: 2.3.0 + '@fastify/rate-limit@10.3.0': + dependencies: + '@lukeed/ms': 2.0.2 + fastify-plugin: 5.1.0 + toad-cache: 3.7.0 + '@fastify/websocket@11.2.0': dependencies: duplexify: 4.1.3 @@ -5065,6 +5092,8 @@ snapshots: dependencies: function-bind: 1.1.2 + helmet@8.1.0: {} + help-me@5.0.0: {} ignore@5.3.2: {}