Skip to content

Installation

Step-by-step deployment of a QS-Bridge cluster on a VPS.


Overview

A complete QS-Bridge deployment consists of five services:

graph TD
    VPS
    VPS --> STDB["SpacetimeDB (port 3000) — database engine"]
    VPS --> Platform["Platform STDB Module — WASM module published to SpacetimeDB"]
    VPS --> Gateway["API Gateway (port 3081) — Hono server, auth + admin API"]
    VPS --> Panel["Panel (static) — React SPA served by gateway or nginx"]
    VPS --> Nginx["Nginx (port 443) — TLS termination, reverse proxy"]

Step 1 — Clone the Repository

cd /opt
git clone https://github.com/QS-Zuq
cd qs-bridge

Step 2 — Start SpacetimeDB

# Start the SpacetimeDB server
spacetime start

# Verify it's running
spacetime version
# Should show: spacetimedb-cli 1.1.x

SpacetimeDB runs on port 3000 by default. For production, consider running it as a systemd service:

# /etc/systemd/system/spacetimedb.service
[Unit]
Description=SpacetimeDB Server
After=network.target

[Service]
Type=simple
User=spacetimedb
ExecStart=/usr/local/bin/spacetime start
Restart=always
RestartSec=5
Environment=SPACETIMEDB_LOG=info

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now spacetimedb

Step 3 — Build and Publish the Platform Module

cd stdb-module

# Build the WASM module
spacetime build

# Publish to local SpacetimeDB
spacetime publish qs-bridge

# Verify
spacetime sql qs-bridge "SELECT * FROM module_config"

The init reducer runs automatically on first publish, creating the ModuleConfig row with owner_identity set to the publishing identity.

Step 4 — Bootstrap the First Admin

# Option A: Use the bootstrap script
./panel/bootstrap-admin.sh

# Option B: Manual SQL insert
spacetime sql qs-bridge \
  "INSERT INTO admin_role (player_id, level, granted_by, granted_at) \
   VALUES ('Steam:YOUR_STEAM64_ID', 0, 'Steam:YOUR_STEAM64_ID', 0)"

Admin levels: 0 = SuperAdmin (Owner), 1 = Admin, 2 = Moderator.

Step 5 — Configure Environment

cd panel/api
cp .env.example .env

Edit .env:

# Required
GATEWAY_PORT=3081
GATEWAY_URL=https://panel.qs-zuq.com
JWT_SECRET=$(openssl rand -hex 32)
STDB_HOST=ws://localhost:3000
STDB_TOKEN=$(spacetime identity token)

# Optional: BisectHosting integration
BISECT_API_KEY=your-api-key-here

Step 6 — Build and Start the API Gateway

cd panel/api

# Install dependencies
npm ci

# Start in production
node server.js

For production, create a systemd service:

# /etc/systemd/system/qs-gateway.service
[Unit]
Description=QS-Bridge API Gateway
After=network.target spacetimedb.service

[Service]
Type=simple
User=qs-bridge
WorkingDirectory=/opt/qs-bridge/panel/api
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=5
EnvironmentFile=/opt/qs-bridge/panel/api/.env

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now qs-gateway

Step 7 — Build the Panel

cd panel/ui

# Install dependencies
npm ci

# Build for production
VITE_API_URL=/api \
VITE_STDB_HOST=wss://panel.qs-zuq.com/stdb \
npm run build

# Output: dist/

Step 8 — Configure Nginx

# /etc/nginx/sites-available/qs-panel
server {
    listen 443 ssl http2;
    server_name panel.qs-zuq.com;

    ssl_certificate /etc/letsencrypt/live/panel.qs-zuq.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/panel.qs-zuq.com/privkey.pem;

    # Panel static files
    root /opt/qs-bridge/panel/ui/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    # API gateway proxy
    location /api/ {
        proxy_pass http://127.0.0.1:3081;
        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;
    }

    # Auth routes proxy
    location /auth/ {
        proxy_pass http://127.0.0.1:3081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # SpacetimeDB WebSocket proxy
    location /stdb/ {
        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_read_timeout 86400;
    }

    # Cache static assets (content-hashed)
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

# HTTP → HTTPS redirect
server {
    listen 80;
    server_name panel.qs-zuq.com;
    return 301 https://$host$request_uri;
}
sudo ln -s /etc/nginx/sites-available/qs-panel /etc/nginx/sites-enabled/
sudo certbot --nginx -d panel.qs-zuq.com
sudo systemctl reload nginx

Step 9 — Deploy the Bridge (per game server)

On each game server:

# Copy the bridge library
scp libqsbridge.so gameserver:/opt/qs-bridge/

# Configure environment
cat > /opt/qs-bridge/bridge.env << 'EOF'
QSB_SERVER_ID=server-01
QSB_STDB_HOST=ws://your-vps:3000
QSB_STDB_MODULE=qs-bridge
QSB_HMAC_SECRET=your-shared-secret
EOF

# Start game server with bridge
source /opt/qs-bridge/bridge.env
LD_PRELOAD=/opt/qs-bridge/libqsbridge.so ./GameServer-Linux-Shipping

Verify Deployment

# 1. SpacetimeDB is running
spacetime sql qs-bridge "SELECT * FROM module_config"

# 2. Gateway is healthy
curl -s http://localhost:3081/api/health | jq .

# 3. Panel is accessible
curl -sI https://panel.qs-zuq.com

# 4. Bridge registered (after starting a game server)
spacetime sql qs-bridge "SELECT server_id, status, online_count FROM server_registry"

Service Summary

Service Port Managed By Auto-Restart
SpacetimeDB 3000 systemd
API Gateway 3081 systemd
Nginx 443/80 systemd
Bridge (.so) Game server Via game server