0892b8f19c
Audit-driven; goal: one user can't exhaust the box or starve the shared Traefik stack. Resource containment: - compose: mem_limit 512m, memswap_limit, pids_limit 200, cpus 1.0 (blast radius = container) - Enforce per-user cloud quota (default 30MB, admin override) on /api/save + /api/characters → 413 - Cap published-character size (2MB); cap rooms (200), images/room (40), image bytes/room (40MB) - Cap WS image dataUrl length in the wire schema; per-IP WS connection cap (20) - MAX_USERS registration ceiling → 503 when full Availability + correctness: - Async crypto.scrypt (was scryptSync) so password hashing can't freeze the event loop; constant-time on unknown users to avoid username probing - trustProxy so req.ip is the real client behind Traefik — rate limits are per-IP, not global - Tighter auth-endpoint bucket (10/min) on /register + /login; prune stale rate buckets - O(1) token->user index; log persist() failures instead of swallowing them - Trim /healthz info; surface quota in /api/usage + the admin dashboard storage bar - Client surfaces 413 (quota) / 429 (rate) clearly Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
1.6 KiB
YAML
49 lines
1.6 KiB
YAML
# Self-contained compose project for ttrpg.briggen.dev.
|
|
# Deploy: rsync the repo to /root/briggen-dev/ttrpg/, place this file there as
|
|
# docker-compose.yml, then `docker compose up -d --build`.
|
|
# Joins the EXISTING external `proxy` network — the shared Traefik stack is untouched.
|
|
name: ttrpg
|
|
|
|
services:
|
|
ttrpg:
|
|
build: .
|
|
container_name: ttrpg
|
|
restart: unless-stopped
|
|
environment:
|
|
- PORT=8787
|
|
- STATIC_DIR=/app/dist
|
|
- NODE_ENV=production
|
|
- DATA_DIR=/data
|
|
- ALLOWED_ORIGINS=https://ttrpg.briggen.dev
|
|
# comma-separated usernames that can see the admin panel (set to your account)
|
|
- ADMIN_USERS=nilsb
|
|
# default per-user cloud storage cap (bytes); admins override per user in the dashboard
|
|
- DEFAULT_QUOTA_BYTES=31457280 # 30 MB
|
|
# hard ceiling on accounts so open registration can't fill the disk
|
|
- MAX_USERS=500
|
|
volumes:
|
|
- ttrpg-data:/data
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
# Hard caps so a runaway session can't starve the host or the shared Traefik
|
|
# stack — the blast radius stays this container, then `restart: unless-stopped`.
|
|
mem_limit: 512m
|
|
memswap_limit: 512m
|
|
pids_limit: 200
|
|
cpus: 1.0
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.ttrpg.rule=Host(`ttrpg.briggen.dev`)"
|
|
- "traefik.http.routers.ttrpg.entrypoints=websecure"
|
|
- "traefik.http.routers.ttrpg.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.ttrpg.loadbalancer.server.port=8787"
|
|
networks:
|
|
- proxy
|
|
|
|
volumes:
|
|
ttrpg-data:
|
|
|
|
networks:
|
|
proxy:
|
|
external: true
|