chore: initial commit for main

This commit is contained in:
hibna 2026-02-22 09:52:38 +03:00
parent 124e4f8921
commit c926613ee0
18 changed files with 1547 additions and 14 deletions

12
.dockerignore Normal file
View File

@ -0,0 +1,12 @@
node_modules
**/node_modules
**/dist
**/target
**/.turbo
.git
.env
.env.*
!.env.example
*.md
.vscode
.idea

View File

@ -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

97
.github/workflows/ci.yml vendored Normal file
View File

@ -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 .

630
INSTALLATION.md Normal file
View File

@ -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=<your-64-byte-hex>
JWT_REFRESH_SECRET=<another-64-byte-hex>
# 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=<generate-with-openssl-rand-hex-64>
JWT_REFRESH_SECRET=<generate-another-secret>
# Database
DB_USER=gamepanel
DB_PASSWORD=<strong-random-password>
DB_NAME=gamepanel
# Redis
REDIS_PASSWORD=<strong-random-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: "<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"
```
### 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:<your-password>@localhost:5432/gamepanel pnpm db:migrate
DATABASE_URL=postgresql://gamepanel:<your-password>@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
# <!DOCTYPE html>...
```
### 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: "<token-from-panel>"
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 <user>`
- 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 |

298
README.md Normal file
View File

@ -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.

47
apps/api/Dockerfile Normal file
View File

@ -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"]

View File

@ -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:*",

View File

@ -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,
});
});

28
apps/daemon/Dockerfile Normal file
View File

@ -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"]

37
apps/web/Dockerfile Normal file
View File

@ -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;"]

54
apps/web/nginx.conf Normal file
View File

@ -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;
}
}

View File

@ -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 (
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<BrowserRouter>
@ -118,5 +120,6 @@ export function App() {
<Toaster position="bottom-right" richColors />
</TooltipProvider>
</QueryClientProvider>
</ErrorBoundary>
);
}

View File

@ -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<Props, State> {
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 (
<div className="flex min-h-[400px] flex-col items-center justify-center gap-4 p-8">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10">
<AlertTriangle className="h-8 w-8 text-destructive" />
</div>
<div className="text-center">
<h2 className="text-lg font-semibold">Something went wrong</h2>
<p className="mt-1 max-w-md text-sm text-muted-foreground">
{this.state.error?.message || 'An unexpected error occurred'}
</p>
</div>
<div className="flex gap-2">
<button
onClick={this.handleReset}
className="inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
>
<RefreshCw className="h-4 w-4" />
Try Again
</button>
<button
onClick={() => window.location.reload()}
className="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted"
>
Reload Page
</button>
</div>
</div>
);
}
return this.props.children;
}
}

13
daemon-config.yml Normal file
View File

@ -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"

28
docker-compose.dev.yml Normal file
View File

@ -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:

View File

@ -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:

View File

@ -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();

View File

@ -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: {}