diff --git a/README.md b/README.md index cd658ac..2151bea 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A full-stack app in one repo: - **Backend** — Node.js + Express REST API (layered `router → controller → model → db`), MariaDB, a provider-agnostic session layer (JWT cookie for web, bearer tokens for mobile, pluggable SSO). - **Frontend** — React + Vite single-page app (public site, wiki, and the admin panel), dark "gothic" theme (Cinzel + Georgia). -- **Deploy** — Docker Compose (app + MariaDB) behind a Pangolin reverse proxy. Express serves the built SPA in production. +- **Deploy** — Docker Compose (app + MariaDB) behind a reverse proxy (Pangolin, Nginx, Caddy, Traefik, …). Express serves the built SPA in production. - **Shard link** — a live bridge to the in-game ServUO shard through the **uo-link** sidecar ([RunicGateway/link](https://gitea.whitlocktech.com/RunicGateway/link)): the site ingests a live event feed and makes server-side REST calls to show shard status, economy, staff presence, IDOCs, live activity, and per-character sheets. See [Shard integration (uo-link)](#shard-integration-uo-link). The design reference is [BACKEND_DESIGN.md](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/BACKEND_DESIGN.md) (API contract, schema, security), in the [**RunicGateway/docs**](https://gitea.whitlocktech.com/RunicGateway/docs) repo — where all project documentation now lives. @@ -32,7 +32,7 @@ The design reference is [BACKEND_DESIGN.md](https://gitea.whitlocktech.com/Runic - [Environment variables](#environment-variables) - [Security](#security) - [Logging](#logging) -- [Deployment behind Pangolin](#deployment-behind-pangolin) +- [Deployment behind a reverse proxy](#deployment-behind-a-reverse-proxy) --- @@ -46,7 +46,7 @@ The design reference is [BACKEND_DESIGN.md](https://gitea.whitlocktech.com/Runic | Frontend | React 18, Vite 5, React Router 6 | | Email | Nodemailer via Gmail OAuth2 (configured in admin), with a `mailto:` fallback | | API docs | OpenAPI 3.0 via `swagger-autogen`, served with `swagger-ui-express` at `/api/docs` | -| Deploy | Docker Compose, Pangolin reverse proxy | +| Deploy | Docker Compose, any reverse proxy (Pangolin, Nginx, Caddy, Traefik, …) | --- @@ -355,7 +355,7 @@ Copy `.env.example` (Compose) or `server/.env.example` (local) and fill in. **`. | `DB_ROOT_PASSWORD` | — | MariaDB root (Compose only) | | `JWT_SECRET` | — | **required** — long random string; signs session, mobile, and SSO-flow tokens | | `JWT_EXPIRES_IN` | `1d` | web session token + cookie lifetime | -| `COOKIE_SECURE` | `auto` | `auto` = Secure only over HTTPS (works on LAN HTTP + Pangolin HTTPS) | +| `COOKIE_SECURE` | `auto` | `auto` = Secure only over HTTPS (works on LAN HTTP + proxy HTTPS) | | `COOKIE_NAME` | `rg_token` | changing it on a live instance invalidates existing sessions | | `BRAND_*` | Runic Gateway | instance branding (name, tagline, colors, logo/hero/favicon) — see [Branding](#branding) | | `SECRET_ENC_KEY` | — | **required in prod** — key for AES-256-GCM encryption of stored OAuth client secrets. Dev falls back to a key derived from `JWT_SECRET` (with a warning) | @@ -460,7 +460,7 @@ run this repo as UOMysticmoon. **Platform** - `helmet`, admin routes `noindex` + `robots.txt` disallow, `trust proxy` for correct client IPs - behind Pangolin (see `TRUST_PROXY`), first admin seeded from env (no hardcoded credentials), + behind a reverse proxy (see `TRUST_PROXY`), first admin seeded from env (no hardcoded credentials), `.env` git-ignored. Passwords and request bodies are never logged. Email sends through Gmail OAuth2 configured in the admin (refresh token stored AES-GCM-encrypted, never in env); the contact form falls back to a `mailto:` link when unconfigured. @@ -487,13 +487,42 @@ bind-mounted to `./logs/app.log` and `docker compose logs -f app` shows the cons --- -## Deployment behind Pangolin +## Deployment behind a reverse proxy `docker compose up -d --build` exposes the `app` container on `0.0.0.0:3000` (no `127.0.0.1` -binding) so Pangolin can reach it. Point a Pangolin resource at `app:3000`. Because `COOKIE_SECURE` -defaults to `auto`, the admin login works both directly via the LAN IP over HTTP **and** through -Pangolin over HTTPS — no config change needed. MariaDB stays on the private Compose network -(no published port by default); data persists in the `dbdata` volume, uploads in `uploads`. +binding) so a reverse proxy — Pangolin, Nginx, Caddy, Traefik, etc. — can reach it. Point the +proxy at `app:3000` (or the host's `:3000` if the proxy runs outside Compose) and terminate TLS +there. Because `COOKIE_SECURE` defaults to `auto`, the admin login works both directly via the +LAN IP over HTTP **and** through the proxy over HTTPS — no config change needed. MariaDB stays on +the private Compose network (no published port by default); data persists in the `dbdata` volume, +uploads in `uploads`. + +Set `TRUST_PROXY` so Express reads the real client IP from the proxy's `X-Forwarded-For` header +(see [Environment variables](#environment-variables)) — required for rate limiting, bot scoring, +and correct logging. Forward the standard `X-Forwarded-For` and `X-Forwarded-Proto` headers from +your proxy. + +Minimal proxy examples: + +```nginx +# Nginx +location / { + proxy_pass http://app:3000; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} +``` + +```caddy +# Caddy — Caddyfile (automatic HTTPS; forwards X-Forwarded-* by default) +your.domain { + reverse_proxy app:3000 +} +``` + +**Pangolin:** create a resource targeting `app:3000`; it forwards the required headers and +terminates HTTPS out of the box, so no extra configuration is needed. ---