573 lines
18 KiB
Markdown
573 lines
18 KiB
Markdown
# Collaboration Server Setup Guide
|
||
|
||
This guide explains how to set up a WebSocket server for yaze's real-time collaboration feature, enabling multiple users to edit ROMs together.
|
||
|
||
## Quick Start with yaze-server
|
||
|
||
The official collaboration server is **[yaze-server](https://github.com/scawful/yaze-server)**, a Node.js WebSocket server with:
|
||
- Real-time session management
|
||
- AI agent integration (Gemini/Genkit)
|
||
- ROM synchronization and diff broadcasting
|
||
- Rate limiting and security features
|
||
|
||
### Local Development
|
||
```bash
|
||
git clone https://github.com/scawful/yaze-server.git
|
||
cd yaze-server
|
||
npm ci
|
||
npm start
|
||
# Server runs on ws://localhost:8765 (default port 8765)
|
||
```
|
||
|
||
### Production Deployment
|
||
For production, deploy yaze-server behind an SSL proxy when possible:
|
||
- **halext-server**: `ws://org.halext.org:8765` (pm2 process `yaze-collab`, no TLS on 8765 today; front with nginx/Caddy for `wss://` if desired)
|
||
- **Self-hosted**: Deploy to Railway, Render, Fly.io, or your own VPS
|
||
|
||
### Current halext deployment (ssh halext-server)
|
||
- Process: pm2 `yaze-collab`
|
||
- Port: `8765` (plain WS/HTTP; add TLS proxy for WSS)
|
||
- Health: `http://org.halext.org:8765/health`, metrics at `/metrics`
|
||
- AI: enable with `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT` + `ENABLE_AI_AGENT=true`
|
||
|
||
### Server v2.1 Features
|
||
- **Persistence**: Configurable SQLite storage (`SQLITE_DB_PATH` env var)
|
||
- **Admin API**: Protected endpoints for session/room management
|
||
- **Enhanced Health**: AI status, TLS detection, persistence info in `/health`
|
||
- **Configurable Limits**: Tunable rate limits via environment variables
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
The yaze web app (WASM build) supports real-time collaboration through WebSocket connections. Since GitHub Pages only serves static files, you'll need a separate WebSocket server to enable this feature.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||
│ User A │ │ WebSocket │ │ User B │
|
||
│ (Browser) │◄───►│ Server │◄───►│ (Browser) │
|
||
│ yaze WASM │ │ (Your Server) │ │ yaze WASM │
|
||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||
```
|
||
|
||
## Protocol Specification
|
||
|
||
The halext deployment (`yaze-collab` pm2 on port 8765) runs the official **yaze-server v2.0** and speaks two compatible protocols:
|
||
- **WASM compatibility protocol** – used by the current web/WASM build (flat `type` JSON messages, no `payload` wrapper).
|
||
- **Session/AI protocol** – used by advanced clients (desktop/editor + AI/ROM sync/proposals) with `{ type, payload }` envelopes.
|
||
|
||
### WASM compatibility protocol (web build)
|
||
All messages are JSON with a `type` field and flat attributes.
|
||
|
||
**Client → Server**
|
||
- `create`: `room`, `name?`, `user`, `user_id`, `color?`, `password?`
|
||
- `join`: `room`, `user`, `user_id`, `color?`, `password?`
|
||
- `leave`: `room`, `user_id`
|
||
- `change`: `room`, `user_id`, `offset`, `old_data`, `new_data`, `timestamp?`
|
||
- `cursor`: `room`, `user_id`, `editor`, `x`, `y`, `map_id`
|
||
- `ping`: optional keep-alive (`{ "type": "ping" }`)
|
||
|
||
**Server → Client**
|
||
- `create_response`: `{ "type": "create_response", "success": true, "session_name": "..." }`
|
||
- `join_response`: `{ "type": "join_response", "success": true, "session_name": "..." }`
|
||
- `users`: `{ "type": "users", "list": [{ "id": "...", "name": "...", "color": "#4ECDC4", "active": true }] }`
|
||
- `change`: Echoed to room with `timestamp` added
|
||
- `cursor`: Broadcast presence updates
|
||
- `error`: `{ "type": "error", "message": "...", "payload": { "error": "..." } }`
|
||
- `pong`: `{ "type": "pong", "payload": { "timestamp": 1700000000000 } }`
|
||
|
||
**Notes**
|
||
- Passwords are supported (`password` hashed server-side); rooms are deleted when empty.
|
||
- Rate limits: 100 messages/min/IP; 10 join/host attempts/min/IP.
|
||
- Size limits: ROM diffs ≤ 5 MB, snapshots ≤ 10 MB. Heartbeat every 30s terminates dead sockets.
|
||
|
||
### Session/AI protocol (advanced clients)
|
||
Messages use `{ "type": "...", "payload": { ... } }`.
|
||
|
||
**Key client messages**
|
||
- `host_session`: `session_name`, `username`, `rom_hash?`, `ai_enabled? (default true)`, `session_password?`
|
||
- `join_session`: `session_code`, `username`, `session_password?`
|
||
- `chat_message`: `sender`, `message`, `message_type?`, `metadata?`
|
||
- `rom_sync`: `sender`, `diff_data` (base64), `rom_hash`
|
||
- `snapshot_share`: `sender`, `snapshot_data` (base64), `snapshot_type`
|
||
- `proposal_share` / `proposal_vote` / `proposal_update`
|
||
- `ai_query`: `username`, `query` (requires `ENABLE_AI_AGENT` plus `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT`)
|
||
- `leave_session`, `ping`
|
||
|
||
**Key server broadcasts**
|
||
- `session_hosted`, `session_joined`, `participant_joined`, `participant_left`
|
||
- `chat_message`, `rom_sync`, `snapshot_shared`
|
||
- `proposal_shared`, `proposal_vote_received`, `proposal_updated`
|
||
- `ai_response` (only when AI is enabled and configured)
|
||
- `pong`, `error`, `server_shutdown`
|
||
|
||
See `yaze-server/README.md` for full payload examples.
|
||
|
||
---
|
||
|
||
## Deployment Options
|
||
|
||
### Self-Hosted (VPS/Dedicated Server)
|
||
|
||
1. Install Node.js 18+
|
||
2. Clone/copy the server code and install deps:
|
||
```bash
|
||
npm ci
|
||
```
|
||
3. Configure environment variables:
|
||
|
||
**Core Settings:**
|
||
```bash
|
||
PORT=8765 # WebSocket/HTTP port (default: 8765)
|
||
ENABLE_AI_AGENT=true # Enable AI query handling (default: true)
|
||
GEMINI_API_KEY=your_api_key # Gemini API key for AI responses
|
||
AI_AGENT_ENDPOINT=http://... # Alternative: external AI endpoint
|
||
```
|
||
|
||
**Persistence (v2.1+):**
|
||
```bash
|
||
SQLITE_DB_PATH=/var/lib/yaze-collab.db # File-based persistence (default: :memory:)
|
||
```
|
||
|
||
**Rate Limiting:**
|
||
```bash
|
||
RATE_LIMIT_MAX_MESSAGES=100 # Messages per minute per IP (default: 100)
|
||
JOIN_LIMIT_MAX_ATTEMPTS=10 # Join/host attempts per minute per IP (default: 10)
|
||
```
|
||
|
||
**Admin API:**
|
||
```bash
|
||
ADMIN_API_KEY=your_secret_key # Protect admin endpoints (optional)
|
||
```
|
||
|
||
4. Run with PM2 for process management:
|
||
```bash
|
||
npm install -g pm2
|
||
pm2 start server.js --name yaze-collab --env production
|
||
pm2 save
|
||
```
|
||
|
||
5. Add TLS reverse proxy (recommended)
|
||
|
||
**nginx example (translate `wss://` to local `ws://localhost:8765`):**
|
||
```nginx
|
||
server {
|
||
listen 443 ssl;
|
||
server_name collab.yourdomain.com;
|
||
|
||
ssl_certificate /path/to/cert.pem;
|
||
ssl_certificate_key /path/to/key.pem;
|
||
|
||
location /ws {
|
||
proxy_pass http://localhost:8765;
|
||
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;
|
||
proxy_send_timeout 86400;
|
||
}
|
||
}
|
||
```
|
||
**Caddy example:**
|
||
```caddy
|
||
collab.yourdomain.com {
|
||
reverse_proxy /ws localhost:8765 {
|
||
header_up Upgrade {>Upgrade}
|
||
header_up Connection {>Connection}
|
||
}
|
||
tls you@example.com
|
||
}
|
||
```
|
||
**Why:** avoids mixed-content errors in browsers, encrypts ROM diffs/chat/passwords, and centralizes cert/ALPN handling.
|
||
|
||
### Platform-as-a-Service
|
||
|
||
| Platform | Pros | Cons |
|
||
|----------|------|------|
|
||
| **Railway** | Easy deploy, free tier | Limited free hours |
|
||
| **Render** | Free tier, auto-deploy | Spins down on inactivity |
|
||
| **Fly.io** | Global edge, generous free | More complex setup |
|
||
| **Deno Deploy** | Free, edge deployment | Deno runtime only |
|
||
| **Cloudflare Workers** | Free tier, global edge | Durable Objects cost |
|
||
|
||
---
|
||
|
||
## Client Configuration
|
||
|
||
### Method 1: JavaScript Configuration (Recommended)
|
||
|
||
Add before loading yaze:
|
||
```html
|
||
<script>
|
||
window.YAZE_CONFIG = {
|
||
collaboration: {
|
||
serverUrl: 'ws://org.halext.org:8765' // use wss:// if you front with TLS
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### Method 2: Meta Tag
|
||
|
||
```html
|
||
<meta name="yaze-collab-server" content="ws://org.halext.org:8765">
|
||
```
|
||
|
||
### Method 3: Runtime Configuration
|
||
|
||
In your integration code:
|
||
```cpp
|
||
auto& collab = WasmCollaboration::GetInstance();
|
||
collab.SetWebSocketUrl("ws://org.halext.org:8765");
|
||
```
|
||
|
||
---
|
||
|
||
## Security Considerations
|
||
|
||
- Transport: terminate TLS in front of the server (`wss://`). The halext deployment currently runs plain `ws://org.halext.org:8765`; add nginx/Caddy to secure it.
|
||
- Built-in guardrails: 100 messages/min/IP, 10 join/host attempts/min/IP, 5 MB ROM diff limit, 10 MB snapshot limit, 30s heartbeat that drops dead sockets.
|
||
- Passwords: supported on both protocols (`password` for WASM, `session_password` for full sessions). Hashing is SHA-256 on the server side.
|
||
- AI: only enabled when `ENABLE_AI_AGENT=true` **and** `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT` is set. Leave unset to disable AI endpoints.
|
||
- Persistence: halext uses in-memory SQLite (sessions reset on restart). For durability, run sqlite on disk (`SQLITE_DB_PATH=/var/lib/yaze-collab.db`) or swap to Postgres/MySQL with a lightweight adapter. Add backups/retention for audit.
|
||
- Authentication: front the service with an auth gateway if you need verified identities; yaze-server does not issue tokens itself.
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
- **Handshake issues:** Match the scheme to the deployment. halext runs `ws://org.halext.org:8765`; use `wss://` only when you have a TLS proxy forwarding `Upgrade` headers.
|
||
- **Health checks:** `curl http://org.halext.org:8765/health` and `/metrics` to confirm the service is live.
|
||
- **TLS errors:** If you front with nginx/Caddy, ensure HTTP/1.1, `Upgrade`/`Connection` headers, and a valid certificate. Remove `wss://` if you have not enabled TLS.
|
||
- **Disconnects/rate limits:** Server sends heartbeats every 30s and enforces limits. Check `pm2 logs yaze-collab` on halext for details.
|
||
- **Performance:** Keep diffs under 5 MB, snapshots under 10 MB, and batch cursor updates on the client. Enable compression at the proxy if needed.
|
||
|
||
---
|
||
|
||
## Operations Playbook (halext-friendly)
|
||
|
||
- **Status:** `curl http://org.halext.org:8765/health` and `/metrics`; add `/metrics` scrape to Prometheus if available.
|
||
- **Logs:** `pm2 logs yaze-collab` (rotate externally if needed).
|
||
- **Restart/Redeploy:** `pm2 restart yaze-collab`; `pm2 list` to verify uptime.
|
||
- **Admin actions:** Use Admin API (see below) or block abusive IPs at the proxy.
|
||
- **Scaling path:** add Redis pub/sub for multi-instance broadcast; place proxy in front with sticky room affinity if you shard.
|
||
|
||
---
|
||
|
||
## Admin API (v2.1+)
|
||
|
||
Protected endpoints for server administration. Set `ADMIN_API_KEY` to require authentication.
|
||
|
||
### Authentication
|
||
Include the key in requests:
|
||
```bash
|
||
curl -H "X-Admin-Key: your_secret_key" http://localhost:8765/admin/sessions
|
||
# Or as query param: http://localhost:8765/admin/sessions?admin_key=your_secret_key
|
||
```
|
||
|
||
### Endpoints
|
||
|
||
**List all sessions/rooms:**
|
||
```bash
|
||
GET /admin/sessions
|
||
# Response: { sessions: [...], wasm_rooms: [...], total_connections: N }
|
||
```
|
||
|
||
**List users in a session:**
|
||
```bash
|
||
GET /admin/sessions/:code/users
|
||
# Response: { code: "ABC123", type: "full"|"wasm", users: [...] }
|
||
```
|
||
|
||
**Close a session (kick all users):**
|
||
```bash
|
||
DELETE /admin/sessions/:code
|
||
# Body: { "reason": "Maintenance" } (optional)
|
||
# Response: { success: true, code: "ABC123", reason: "..." }
|
||
```
|
||
|
||
**Kick a specific user:**
|
||
```bash
|
||
DELETE /admin/sessions/:code/users/:userId
|
||
# Body: { "reason": "Violation of rules" } (optional)
|
||
# Response: { success: true, code: "ABC123", userId: "user-123", reason: "..." }
|
||
```
|
||
|
||
**Broadcast message to session:**
|
||
```bash
|
||
POST /admin/sessions/:code/broadcast
|
||
# Body: { "message": "Server maintenance in 5 minutes", "message_type": "admin" }
|
||
# Response: { success: true, code: "ABC123", recipients: N }
|
||
```
|
||
|
||
---
|
||
|
||
## Halext TLS Deployment Guide
|
||
|
||
Step-by-step guide to add WSS (TLS) to the halext deployment.
|
||
|
||
### Prerequisites
|
||
- SSH access to halext-server
|
||
- Domain DNS pointing to server (e.g., `collab.halext.org` or use existing `org.halext.org`)
|
||
- Certbot or existing SSL certificates
|
||
|
||
### Option A: nginx reverse proxy
|
||
|
||
1. **Install nginx (if not present):**
|
||
```bash
|
||
sudo apt update && sudo apt install nginx certbot python3-certbot-nginx
|
||
```
|
||
|
||
2. **Create nginx config:**
|
||
```bash
|
||
sudo nano /etc/nginx/sites-available/yaze-collab
|
||
```
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name collab.halext.org; # or org.halext.org
|
||
|
||
# Redirect HTTP to HTTPS
|
||
return 301 https://$server_name$request_uri;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name collab.halext.org;
|
||
|
||
ssl_certificate /etc/letsencrypt/live/collab.halext.org/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/collab.halext.org/privkey.pem;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||
|
||
# WebSocket proxy
|
||
location / {
|
||
proxy_pass http://127.0.0.1:8765;
|
||
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;
|
||
proxy_read_timeout 86400;
|
||
proxy_send_timeout 86400;
|
||
}
|
||
}
|
||
```
|
||
|
||
3. **Enable site and get certificate:**
|
||
```bash
|
||
sudo ln -s /etc/nginx/sites-available/yaze-collab /etc/nginx/sites-enabled/
|
||
sudo certbot --nginx -d collab.halext.org
|
||
sudo nginx -t && sudo systemctl reload nginx
|
||
```
|
||
|
||
4. **Update client config to use WSS:**
|
||
```javascript
|
||
window.YAZE_CONFIG = {
|
||
collaboration: { serverUrl: 'wss://collab.halext.org' }
|
||
};
|
||
```
|
||
|
||
### Option B: Caddy (simpler, auto-TLS)
|
||
|
||
1. **Install Caddy:**
|
||
```bash
|
||
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
|
||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
|
||
sudo apt update && sudo apt install caddy
|
||
```
|
||
|
||
2. **Create Caddyfile:**
|
||
```bash
|
||
sudo nano /etc/caddy/Caddyfile
|
||
```
|
||
```caddy
|
||
collab.halext.org {
|
||
reverse_proxy localhost:8765
|
||
# Caddy handles TLS automatically
|
||
}
|
||
```
|
||
|
||
3. **Reload Caddy:**
|
||
```bash
|
||
sudo systemctl reload caddy
|
||
```
|
||
|
||
### Verify TLS is working
|
||
|
||
```bash
|
||
# Check health endpoint shows TLS detected
|
||
curl -s https://collab.halext.org/health | jq '.tls'
|
||
# Expected: { "detected": true, "note": "Request via TLS proxy" }
|
||
|
||
# Test WebSocket connection
|
||
wscat -c wss://collab.halext.org
|
||
```
|
||
|
||
---
|
||
|
||
## PM2 Ecosystem File
|
||
|
||
For more control, use a PM2 ecosystem file:
|
||
|
||
**ecosystem.config.js:**
|
||
```javascript
|
||
module.exports = {
|
||
apps: [{
|
||
name: 'yaze-collab',
|
||
script: 'server.js',
|
||
instances: 1,
|
||
autorestart: true,
|
||
watch: false,
|
||
max_memory_restart: '500M',
|
||
env: {
|
||
NODE_ENV: 'development',
|
||
PORT: 8765
|
||
},
|
||
env_production: {
|
||
NODE_ENV: 'production',
|
||
PORT: 8765,
|
||
SQLITE_DB_PATH: '/var/lib/yaze-collab/sessions.db',
|
||
ENABLE_AI_AGENT: 'true',
|
||
// GEMINI_API_KEY: 'your_key_here',
|
||
// ADMIN_API_KEY: 'your_admin_key'
|
||
}
|
||
}]
|
||
};
|
||
```
|
||
|
||
**Usage:**
|
||
```bash
|
||
pm2 start ecosystem.config.js --env production
|
||
pm2 save
|
||
pm2 startup # Enable startup on boot
|
||
```
|
||
|
||
---
|
||
|
||
## Persistence & Backup
|
||
|
||
### Enable file-based persistence
|
||
```bash
|
||
# Create data directory
|
||
sudo mkdir -p /var/lib/yaze-collab
|
||
sudo chown $(whoami) /var/lib/yaze-collab
|
||
|
||
# Set environment variable
|
||
export SQLITE_DB_PATH=/var/lib/yaze-collab/sessions.db
|
||
pm2 restart yaze-collab --update-env
|
||
```
|
||
|
||
### Backup strategy
|
||
```bash
|
||
# Daily backup cron job
|
||
echo "0 3 * * * sqlite3 /var/lib/yaze-collab/sessions.db '.backup /backups/yaze-collab-$(date +%Y%m%d).db'" | crontab -
|
||
|
||
# Retain last 7 days
|
||
echo "0 4 * * * find /backups -name 'yaze-collab-*.db' -mtime +7 -delete" | crontab -e
|
||
```
|
||
|
||
### Health endpoint (v2.1+)
|
||
The `/health` endpoint now reports persistence status:
|
||
```json
|
||
{
|
||
"status": "healthy",
|
||
"version": "2.1",
|
||
"persistence": {
|
||
"type": "file",
|
||
"path": "/var/lib/yaze-collab/sessions.db"
|
||
},
|
||
"ai": {
|
||
"enabled": true,
|
||
"configured": true,
|
||
"provider": "gemini"
|
||
},
|
||
"tls": {
|
||
"detected": true,
|
||
"note": "Request via TLS proxy"
|
||
}
|
||
}
|
||
```
|
||
|
||
## Client UX hints
|
||
|
||
- Surface server status in the web UI by calling `/health` once on load and showing: server reachable, AI enabled/disabled (from health/metrics), and whether TLS is in use.
|
||
- Default `window.YAZE_CONFIG.collaboration.serverUrl` to `wss://collab.yourdomain.com/ws` when a TLS proxy is present; fall back to `ws://localhost:8765` for local dev.
|
||
- Show a small banner when AI is disabled or when the connection is downgraded to plain WS to set user expectations.
|
||
|
||
---
|
||
|
||
## Example: Complete Docker Deployment
|
||
|
||
**Dockerfile:**
|
||
```dockerfile
|
||
FROM node:18-alpine
|
||
WORKDIR /app
|
||
COPY package*.json ./
|
||
RUN npm ci --only=production
|
||
COPY server.js .
|
||
EXPOSE 8765
|
||
CMD ["node", "server.js"]
|
||
```
|
||
|
||
**docker-compose.yml:**
|
||
```yaml
|
||
version: '3.8'
|
||
services:
|
||
collab:
|
||
build: .
|
||
ports:
|
||
- "8765:8765"
|
||
restart: unless-stopped
|
||
environment:
|
||
- NODE_ENV=production
|
||
- ENABLE_AI_AGENT=true
|
||
# Uncomment one of the following if AI responses are desired
|
||
# - GEMINI_API_KEY=your_api_key
|
||
# - AI_AGENT_ENDPOINT=http://ai-service:5000
|
||
```
|
||
|
||
Deploy:
|
||
```bash
|
||
docker-compose up -d
|
||
```
|
||
|
||
---
|
||
|
||
## Testing Your Server
|
||
|
||
Use wscat to test:
|
||
```bash
|
||
npm install -g wscat
|
||
wscat -c ws://org.halext.org:8765
|
||
|
||
# Send create message
|
||
{"type":"create","room":"TEST01","name":"Test","user":"TestUser","user_id":"test-123","color":"#FF0000"}
|
||
|
||
# Check response
|
||
# < {"type":"create_response","success":true,"session_name":"Test"}
|
||
|
||
# Test full session protocol (AI disabled)
|
||
{"type":"host_session","payload":{"session_name":"DocsCheck","username":"tester","ai_enabled":false}}
|
||
```
|
||
|
||
Health check:
|
||
```bash
|
||
curl http://org.halext.org:8765/health
|
||
curl http://org.halext.org:8765/metrics
|
||
```
|
||
|
||
Or use the browser console on your yaze deployment:
|
||
```javascript
|
||
window.YAZE_CONFIG = {
|
||
collaboration: { serverUrl: 'ws://org.halext.org:8765' }
|
||
};
|
||
// Then use the collaboration UI in yaze
|
||
```
|