Files
hibna c1adb94abb Format the repository with Prettier
CI has been running `prettier --check` against a tree that was never
formatted, so the check reported 63 files and failed every run. Nothing
here is a behaviour change: `pnpm lint` and the four typecheck builds
pass exactly as before.

conduit-bringup-artifacts is added to .prettierignore instead. Those
files are captured bring-up reports, not maintained sources; reflowing
them would only churn a record of what happened.
2026-08-02 21:08:12 +03:00

752 lines
23 KiB
Markdown

# 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
# Sync the schema from packages/database/src/schema, then apply the
# hand-written data migrations in packages/database/drizzle/*.sql
pnpm db:migrate
# Seed admin user and default games
pnpm db:seed
```
All three steps are idempotent, so re-running them after a `git pull` is the
normal way to pick up schema and default-game changes.
After seeding, you'll have:
- **Admin account**: `admin@gamepanel.local` / `admin123`
- **Games**: Minecraft Java & Bedrock, CS2, Terraria, Rust, Satisfactory,
FiveM, ARK: Survival Evolved
### 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
The whole panel comes up with two commands. TLS and domain handling are
deliberately **not** included — the panel serves plain HTTP and you put your own
reverse proxy in front of it (see 2.6).
### 2.1 Install
```bash
git clone https://github.com/your-org/source-gamepanel.git
cd source-gamepanel
./scripts/install.sh
docker compose up -d --build
```
`scripts/install.sh` generates `.env` with fresh secrets, writes a
`daemon-config.yml` with a matching node token, and creates the host data
directories. It never overwrites files that already exist, so it is safe to
re-run.
Then open `http://<server-ip>:80` and sign in with
`admin@gamepanel.local` / `admin123` — change the password immediately.
### 2.2 What gets started
| Service | Port | Description |
| ---------- | -------------------------- | -------------------------------------------------- |
| `postgres` | internal | PostgreSQL database |
| `redis` | internal | Rate limiting & cache |
| `migrate` | — | Applies the schema + seed, then exits |
| `api` | internal | Fastify REST API |
| `web` | `WEB_PORT` (80) | nginx + React SPA, proxies `/api` and `/socket.io` |
| `daemon` | `DAEMON_GRPC_PORT` (50051) | Rust gRPC daemon |
Only `web` and `daemon` publish ports. Postgres, Redis and the API stay on the
internal Compose network.
The `migrate` service runs on every `docker compose up`; all three of its steps
(`drizzle-kit push`, the data migrations, the seed) are idempotent.
### 2.3 Register the node
In the panel, create a node with:
| Field | Value |
| ------------ | -------------------------------------------------- |
| FQDN | `host.docker.internal` (or the host's IP/hostname) |
| gRPC port | the `DAEMON_GRPC_PORT` from `.env` |
| Daemon token | the `DAEMON_TOKEN` from `.env` |
### 2.4 Where game server files live
`DAEMON_DATA_PATH` in `.env` (default `/var/lib/gamepanel/servers`) is a **host**
directory. The daemon runs in a container but creates game containers through
the host's Docker socket, so their bind mounts are resolved by the host, not by
the daemon container.
That is why the same path is passed twice — once as the daemon's own bind mount
and once as `DAEMON_HOST_DATA_PATH`. If you change `DAEMON_DATA_PATH`, both
follow automatically. Do not replace the bind mount with a named volume: the
daemon and the game servers would then read and write two different
directories, and files edited in the panel would never reach the game.
### 2.5 Verify
```bash
docker compose ps
docker compose logs -f api
docker compose logs -f daemon
curl -s http://localhost/api/health
# {"status":"ok","timestamp":"..."}
```
### 2.6 TLS, domain and reverse proxy
The panel intentionally ships without certificate handling. Terminate TLS in
whatever proxy you already run and forward to `WEB_PORT`. WebSocket upgrades
must be forwarded too, otherwise the live console will not connect.
Set `CORS_ORIGIN` in `.env` to the exact origin users open in the browser, then
`docker compose up -d` to apply it.
Caddy (`Caddyfile`):
```
panel.example.com {
reverse_proxy 127.0.0.1:80
}
```
nginx:
```nginx
server {
listen 443 ssl;
server_name panel.example.com;
ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:80;
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-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
If the proxy runs on the same host, bind the panel to loopback only by setting
`WEB_PORT=127.0.0.1:8080` in `.env`.
### 2.7 Updating
```bash
git pull
docker compose up -d --build
```
The `migrate` service applies schema and seed changes on every start, so no
extra step is needed.
### 2.8 Upgrading from a pre-`install.sh` deployment
Older `docker-compose.yml` versions stored the daemon's server directory in a
named volume (`daemon_data`). That never matched what the game containers
actually used: their bind mounts were resolved by the host, so the real game
files ended up in `/var/lib/gamepanel/servers` on the host while the panel read
and wrote the named volume. Editing a config in the panel appeared to work and
then had no effect, and files could look like they reset themselves.
The compose file now bind-mounts the host directory directly, so after
upgrading, the panel sees the same files the game servers do. Nothing needs to
be moved — the game files were already on the host.
If you had put files into the old named volume through the panel and want them
back, copy them out before removing it:
```bash
docker run --rm -v gamepanel_daemon_data:/from -v /var/lib/gamepanel/servers:/to alpine sh -c 'cp -an /from/. /to/'
docker volume rm gamepanel_daemon_data gamepanel_daemon_backups
```
Also note that `postgres`, `redis` and `api` no longer publish host ports; only
`web` and `daemon` do. If you were proxying straight to `API_PORT`, point your
proxy at `WEB_PORT` instead — nginx forwards `/api` and `/socket.io`.
---
## 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
```
---
## 4. Control Panel Deployment (pre-built images)
Sections 2 and 3 build from a checkout on the server. A control panel does not
have one: it writes a compose file and an `.env` into its own project directory
and runs `docker compose up`. Anything with a `build:` stanza fails there —
the build context simply is not on disk.
`docker-compose.panel.yml` exists for that case. Every service references a
published image, so the stack installs on a server that has never seen this
repository. It was written against [WebPanel](https://gits.hibna.com.tr/hibna/Source-WebPanel)
but nothing in it is panel-specific.
### 4.1 Publish the images
`.github/workflows/ci.yml` pushes four images to this Gitea instance's own
container registry on every `v*` tag:
| Image | Contents |
| ------------------- | -------------------------------------------------------------------- |
| `gamepanel-api` | Fastify API |
| `gamepanel-migrate` | The API Dockerfile's `migrate` stage, run once before the API starts |
| `gamepanel-web` | SPA + nginx, built with `VITE_API_URL=/api` |
| `gamepanel-daemon` | Rust daemon |
Add a `REGISTRY_TOKEN` repository secret with package write scope, then:
```bash
git tag v0.1.0 && git push origin v0.1.0
```
### 4.2 Prepare the host
```bash
sudo mkdir -p /etc/gamepanel /var/lib/gamepanel/servers /var/lib/gamepanel/backups
sudo cp daemon-config.yml /etc/gamepanel/daemon-config.yml
sudo sed -i 's/CHANGE_ME_GENERATE_A_SECURE_TOKEN/'"$(openssl rand -hex 32)"'/' /etc/gamepanel/daemon-config.yml
```
Note the token you generated — the panel needs the same value when you register
the node. If the panel has a file manager, both steps can be done from it.
### 4.3 Install
Paste `docker-compose.panel.yml` into the panel's custom-compose screen and set:
| Variable | Example | Notes |
| ---------------------------------- | --------------------------- | ------------------------------------------------ |
| `REGISTRY` | `gits.hibna.com.tr/hibna` | Namespace holding the four images |
| `TAG` | `v0.1.0` | The tag you pushed |
| `HOST_PORT` | `8096` | **Not 80** if the panel's own web server owns it |
| `DB_PASSWORD`, `REDIS_PASSWORD` | `openssl rand -hex 24` | |
| `JWT_SECRET`, `JWT_REFRESH_SECRET` | `openssl rand -hex 64` | |
| `CORS_ORIGIN` | `https://panel.example.com` | Must match the address the browser uses |
The published port is called `HOST_PORT` because panels commonly reverse-proxy
"the" port of an installation and need to know which one that is when a stack
publishes more than one.
If the registry is private, the host needs `docker login` once — panels pull
anonymously otherwise. On Gitea the package can also be made public while the
repository stays private.
### 4.4 Notes
- **The daemon holds the Docker socket.** That is root-equivalent access to
every container on the machine, the panel's own containers included. Running
the daemon on a separate node — which the multi-node architecture is built
for — keeps the game hosts and the control plane apart.
- **Nothing publishes gRPC on a single host.** The API reaches the daemon over
the compose network as `daemon:50051`. A remote node runs the `daemon`
service on its own machine and publishes `50051` there.
- **Game server ports** are opened by the daemon on the host; a panel with a
default-deny firewall needs an explicit rule for the range you hand out.
---
## 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 |