docs(website): rebrand website-README to Runic Gateway
Sync the extracted website README with the current repo README after the org rename + BRAND_* branding work: UOMysticmoon → Runic Gateway as the default identity (UOMysticmoon retained only as the example first instance), UOM/link → RunicGateway/link, DB/cookie/TOTP defaults updated, and the new Branding section added. Doc-local BACKEND_DESIGN.md links kept relative; the .env.uomysticmoon.example link points at the website repo where it lives. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XmHdsbnLzDMAVQkAoTQSBe
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
# UOMysticmoon Website
|
||||
# Runic Gateway Website
|
||||
|
||||
Public site, wiki, and protected admin panel for the **UOMysticmoon** private Ultima Online
|
||||
shard — a full-stack app in one repo:
|
||||
Public site, wiki, and protected admin panel for a private Ultima Online shard — a
|
||||
full-stack app in one repo. Branding is instance-configurable via `BRAND_*` (see
|
||||
[Branding](#branding)); **UOMysticmoon** is the first instance.
|
||||
|
||||
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.
|
||||
- **Shard link** — a live bridge to the in-game ServUO shard through the **uo-link** sidecar ([UOM/link](https://gitea.whitlocktech.com/UOM/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).
|
||||
- **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](BACKEND_DESIGN.md) (API contract, schema, security).
|
||||
|
||||
@@ -133,14 +136,14 @@ to the backend, so the SPA stays same-origin (cookies work).
|
||||
**1. Start a MariaDB the backend can reach** (published on `localhost:3306`):
|
||||
|
||||
```bash
|
||||
docker run -d --name uomm-db -p 3306:3306 -e MARIADB_DATABASE=uomysticmoon -e MARIADB_USER=uomm -e MARIADB_PASSWORD=devpass -e MARIADB_ROOT_PASSWORD=rootpass mariadb:11
|
||||
docker run -d --name rg-db -p 3306:3306 -e MARIADB_DATABASE=runic_gateway -e MARIADB_USER=runic -e MARIADB_PASSWORD=devpass -e MARIADB_ROOT_PASSWORD=rootpass mariadb:11
|
||||
```
|
||||
|
||||
**2. Configure + start the backend** (terminal 1):
|
||||
|
||||
```bash
|
||||
cp server/.env.example server/.env
|
||||
# Set DB_HOST=127.0.0.1, DB_PORT=3306, DB_USER=uomm, DB_PASSWORD=devpass,
|
||||
# Set DB_HOST=127.0.0.1, DB_PORT=3306, DB_USER=runic, DB_PASSWORD=devpass,
|
||||
# JWT_SECRET=<anything>, ADMIN_USERNAME=admin, ADMIN_PASSWORD=<your password>
|
||||
npm run install-server
|
||||
npm run server # nodemon → http://localhost:3000
|
||||
@@ -249,7 +252,7 @@ actually returns (`400` validation, `401`/`403` auth, `404`, `409` conflicts, `4
|
||||
|
||||
**Authentication in the UI** — click **Authorize** and provide either:
|
||||
|
||||
- `cookieAuth` — the `uomm_token` session cookie (set automatically in the browser after
|
||||
- `cookieAuth` — the session cookie (name `rg_token`, configurable via `COOKIE_NAME`; set automatically in the browser after
|
||||
`POST /api/v1/auth/login`), or
|
||||
- `bearerAuth` — a mobile access token from `POST /api/v1/auth/mobile/login` (sent as
|
||||
`Authorization: Bearer <token>`).
|
||||
@@ -274,7 +277,7 @@ not crash).
|
||||
|
||||
The site is wired to the live in-game world through **uo-link**, a standalone sidecar service that
|
||||
runs next to the ServUO shard. Its source lives in a separate repo:
|
||||
**[UOM/link](https://gitea.whitlocktech.com/UOM/link)**. uo-link speaks the shard's internals and
|
||||
**[RunicGateway/link](https://gitea.whitlocktech.com/RunicGateway/link)**. uo-link speaks the shard's internals and
|
||||
exposes a small, authenticated HTTP + WebSocket API; this website is a *client* of it. The shard
|
||||
itself is never exposed to the internet — only the sidecar is, and only the website's backend talks
|
||||
to it.
|
||||
@@ -282,7 +285,7 @@ to it.
|
||||
### How it works
|
||||
|
||||
```
|
||||
ServUO shard ──▶ uo-link sidecar (UOM/link) ──▶ website backend ──▶ browser
|
||||
ServUO shard ──▶ uo-link sidecar (RunicGateway/link) ──▶ website backend ──▶ browser
|
||||
REST + WebSocket, bearer-auth ingest + REST same-origin JSON/SSE
|
||||
```
|
||||
|
||||
@@ -348,19 +351,20 @@ Copy `.env.example` (Compose) or `server/.env.example` (local) and fill in. **`.
|
||||
| `PORT` | `3000` | server listens on `0.0.0.0:PORT` |
|
||||
| `UPLOAD_DIR` | `<server>/uploads` | where post images are written (`/app/uploads`, volume-mounted, in Compose) |
|
||||
| `DB_HOST` / `DB_PORT` | `db` / `3306` | `db` in Compose; `127.0.0.1` for local dev |
|
||||
| `DB_NAME` / `DB_USER` / `DB_PASSWORD` | `uomysticmoon` / `uomm` / — | app database credentials |
|
||||
| `DB_NAME` / `DB_USER` / `DB_PASSWORD` | `runic_gateway` / `runic` / — | app database credentials |
|
||||
| `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_NAME` | `uomm_token` | |
|
||||
| `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) |
|
||||
| `APP_BASE_URL` | — | public base URL, used to build the SSO OAuth `redirect_uri` (`${APP_BASE_URL}/api/v1/auth/sso/:provider/callback`). Set in prod to match what you register with Google/Discord; if unset it is derived from the request (fine for local dev) |
|
||||
| `MOBILE_ACCESS_TTL` | `15m` | mobile bearer **access** token lifetime (short-lived) |
|
||||
| `MOBILE_REFRESH_TTL_DAYS` | `30` | mobile **refresh** token lifetime (long-lived, rotated on use) |
|
||||
| `TRUST_PROXY` | `1` | reverse-proxy trust for correct `req.ip` / `req.secure` (rate limiting, backoff, bot-ban). Pin to the proxy hop's LAN IP in prod. A blanket `true` is rejected (coerced to `1`) to block `X-Forwarded-For` spoofing |
|
||||
| `DEBUG_TRUST_PROXY` | `0` | `1` logs raw peer address + `X-Forwarded-For` + resolved `req.ip` per request (to verify/refresh the proxy IP). Noisy — leave off |
|
||||
| `TOTP_ISSUER` | `UOMysticmoon` | label shown in authenticator apps for optional per-user 2FA |
|
||||
| `TOTP_ISSUER` | `BRAND_NAME` | label shown in authenticator apps for optional per-user 2FA |
|
||||
| `TOTP_CHALLENGE_TTL` | `5m` | lifetime of the short-lived post-password "awaiting code" step |
|
||||
| `ADMIN_USERNAME` / `ADMIN_PASSWORD` | — | first-admin bootstrap (first boot only) |
|
||||
| _Email_ | — | configured in Admin → Settings → Email (Gmail OAuth2), not via env; recipient = `contact_email` setting |
|
||||
@@ -372,6 +376,32 @@ Copy `.env.example` (Compose) or `server/.env.example` (local) and fill in. **`.
|
||||
|
||||
---
|
||||
|
||||
## Branding
|
||||
|
||||
Instance identity is data, not code — set via `BRAND_*` env vars, so one prebuilt
|
||||
image can run as any shard. With none set, everything renders as **Runic Gateway**.
|
||||
|
||||
| Var | What |
|
||||
|---|---|
|
||||
| `BRAND_NAME` / `BRAND_SHORT_NAME` | display name (full / short-in-prose) |
|
||||
| `BRAND_TAGLINE` / `BRAND_DESCRIPTION` | tagline + meta/OG description |
|
||||
| `BRAND_CONTACT_EMAIL` / `BRAND_URL` | contact + canonical URL (for OG/absolute links) |
|
||||
| `BRAND_ACCENT_COLOR` | theme `--accent` (web) + Discord embed color |
|
||||
| `BRAND_LOGO` / `BRAND_HERO` / `BRAND_FAVICON` | image paths under the `/brand` mount, or absolute URLs |
|
||||
|
||||
**How it flows:** text/colors reach the SPA at runtime through the public settings
|
||||
API (`SiteContext`), so no rebuild is needed; the server templates `index.html`
|
||||
`<title>`/meta/OG/favicon at boot; emails, TOTP issuer, and the Discord bot read
|
||||
`BRAND_*` directly. The admin-editable **site title** and **contact email**
|
||||
settings override `BRAND_NAME` / `BRAND_CONTACT_EMAIL` when set. Image assets are
|
||||
delivered from the `./brand` bind-mount (see `brand/README.md`).
|
||||
|
||||
**UOMysticmoon** is the first instance — [`.env.uomysticmoon.example`](https://gitea.whitlocktech.com/RunicGateway/website/src/branch/main/.env.uomysticmoon.example)
|
||||
holds the exact `BRAND_*` + infra (`DB_NAME`/`DB_USER`/`COOKIE_NAME`) pinning to
|
||||
run this repo as UOMysticmoon.
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
**Session & authorization**
|
||||
|
||||
Reference in New Issue
Block a user