# Runic Gateway **A website + game bridge for private Ultima Online (ServUO) shards.** Runic Gateway is a self-hostable platform that gives a UO shard a public site, wiki, and admin panel — and wires it to the *live in-game world* so the site can show shard status, economy, IDOCs, player activity, per-character sheets, a player-vendor marketplace, and a bestiary built from the shard's own spawn tables, while staff push control commands back into the game. Players get the same public content and self-service on the web **or** a native Android app. Which audience sees which shard data is admin-configurable, and branding is per-instance. One installer sets up the shard side.
--- ## The pieces Runic Gateway is six repositories that deploy together but build independently: | Repo | Language | What it is | |------|----------|------------| | [**website**](https://gitea.whitlocktech.com/RunicGateway/website) | JavaScript (Node + React) | The full-stack app — Express REST API + MariaDB + a React/Vite SPA (public site, wiki, admin panel). This is the thing players and staff actually visit, and the single backend every client talks to. | | [**link**](https://gitea.whitlocktech.com/RunicGateway/link) | Rust | The **uo-link** sidecar. Runs next to the shard, terminates a loopback link from the game, and exposes the authenticated WebSocket + REST API the website consumes. The only network-facing half of the bridge. | | [**servuo-plugins**](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins) | C# | The **ServUO plugin** — the shard side of the bridge. Compiled by ServUO at boot; dials the sidecar over loopback and emits game events / accepts commands. | | [**installer**](https://gitea.whitlocktech.com/RunicGateway/installer) | Rust | The **installer** — one binary per OS that deploys the plugin and the sidecar onto a shard host as a matched, protocol-checked pair, registers the service, and hands you the values the website needs. How you set a shard up. | | [**Android-app**](https://gitea.whitlocktech.com/RunicGateway/Android-app) | Kotlin (Jetpack Compose) | The **native Android client** — public content + player self-service, purely an API client of the website backend. Same features as the browser client *minus* every admin console. Never touches the sidecar or shard. | | [**docs**](https://gitea.whitlocktech.com/RunicGateway/docs) | Markdown | All project documentation — design docs, the wire-protocol spec, the integration guide, the Android plan, research. Start here when you want the *why*. | ## How they fit together ``` ┌─▶ browser same-origin JSON / SSE ServUO shard ──loopback TCP,──▶ uo-link sidecar ──WS + REST──▶ website backend ─┤ (servuo-plugins) newline-JSON (link, Rust) bearer-auth (website, Node) └─▶ Android app REST + push (ntfy) ``` - The **shard is never exposed to the internet.** It only dials `127.0.0.1`. The **sidecar** is the sole network-facing component, and only the website's backend talks to it. - The **website backend** ingests a live event stream from the sidecar (logins, vitals, economy, vendor sales, deaths, IDOC decay, staff audit…) and makes point-in-time REST calls for rosters and character sheets. It then fans that out to browsers over Server-Sent Events — a public channel (safe kinds only) and an admin channel (everything). - The **Android app** is *just another client of the website backend* — it speaks the same public REST API and never talks to the sidecar or shard. It self-configures its server URL on first run, so one build works against any shard, and receives push notifications through the shard's self-hosted **ntfy** relay (no Google Play Services required). - Players **link** a game account to a website account with a one-time in-game code, which is what authorizes character reads. Staff can push town-crier messages and control commands back into the game. - **Accounts are hardened out of the box** — TOTP two-factor with trusted devices, optional SSO (Google / Discord / any OIDC provider, link-only: an external identity must already belong to an account), bot scoring with automatic IP bans, and rate limiting. - **Who sees which shard data is configurable**, not hardcoded: an admin panel decides the audience for each shard surface, and the public SSE channel can never carry a sensitive event kind. The connection between website and sidecar (URL, shared-secret token, protocol version) is **admin-managed in the database**, not env — the installer prints those values and you paste them once into the site's **Admin → Shard** panel. If the sidecar is absent or the shard is down, every shard surface degrades gracefully. --- ## Quick start The core of a live shard is **two** things running: the **website** (site + admin), and the **shard side** — the sidecar plus the plugin, which the **installer** deploys in a single run on the machine that hosts your ServUO server. The **Android-app** is an optional client you point at your running website. ### 1. Website — the site + admin panel Prereqs: **Node.js 20+** and **Docker** (for MariaDB). ```bash git clone https://gitea.whitlocktech.com/RunicGateway/website.git cd website # Start a MariaDB the backend can reach 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 # Configure + start the backend (terminal 1) cp server/.env.example server/.env # set DB_HOST=127.0.0.1, DB_PORT=3306, DB_USER=runic, DB_PASSWORD=devpass, # JWT_SECRET=, ADMIN_USERNAME=admin, ADMIN_PASSWORD= npm run install-server npm run server # nodemon → http://localhost:3000 # Start the frontend (terminal 2) npm run install-client npm run client # Vite → http://localhost:5173 ``` Open **http://localhost:5173**, sign in at **`/admin/login`** with the admin credentials you set, then flip **Maintenance → Live** on the Dashboard. Tables, defaults, and the first admin are created automatically on first boot. > **Production (Docker Compose):** the website ships a production-shaped > `docker-compose.yml` that *pulls* prebuilt `app` + `bot` images and serves the built SPA > from Express — `cp .env.example .env`, fill it in, then > `docker compose pull && docker compose up -d`. See the website README for the full options. ### 2. The shard side — one installer run Prereqs: a **working ServUO install**, currently **stopped**, and Administrator/root. Nothing to build and no Gitea account needed. Grab a binary and `SHA256SUMS` from the [installer releases](https://gitea.whitlocktech.com/RunicGateway/installer/releases) — releases are unsigned, so the checksum is the trust anchor: ```bash sha256sum -c SHA256SUMS --ignore-missing chmod +x runicgateway-installer-linux-x86_64 sudo ./runicgateway-installer-linux-x86_64 install ``` ```powershell # Windows, from an elevated PowerShell .\runicgateway-installer-windows-x86_64.exe install ``` It resolves a published **bundle** — an exact sidecar + plugin pair CI has checked speak the same protocol, rather than "latest of each" — syncs the plugin overlay into your server tree, offers the optional stock-file patch tier, installs the sidecar and registers it as a service under an unprivileged account, and finishes by printing the four values to paste into **Admin → Shard**: ``` Base URL http://:8080 WebSocket URL ws://:8080/ws Protocol version 3 Auth token 4f9c… ``` Keep that binary — it does not install itself, and `doctor`, `update` and `uninstall` are run from it later. Start ServUO, then `./runicgateway-installer-… doctor` checks the deployment end to end, through to *"has the shard actually dialed in?"* — the only check that tells a working bridge from copied files. > Installing by hand is supported too — [INSTALL.md Appendix A](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/installer/INSTALL.md#appendix-a--installing-by-hand) > is the same deployment with `curl`, `tar` and `systemctl`, for a host that cannot run the binary > or for developing on the bridge from a source tree. The full operator guide is > [`docs/installer/INSTALL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/installer/INSTALL.md). ### 3. Android-app — the native client (optional) Prereqs: **JDK 17** and the Android SDK. A single build works against any shard — the app prompts for your website's URL on first run. ```bash git clone https://gitea.whitlocktech.com/RunicGateway/Android-app.git cd Android-app ./gradlew assembleDebug # debug APK → app/build/outputs/apk/debug/ ./gradlew installDebug # install on a connected device / emulator ``` Kotlin + Jetpack Compose (Material 3), min SDK Android 10 (API 29). It surfaces the same public content and player self-service as the browser — home/status, news, wiki, the shard hub, account linking, character sheets — and opts into push notifications through the shard's self-hosted **ntfy** relay. See the [Android-app README](https://gitea.whitlocktech.com/RunicGateway/Android-app) and the design contract in [`docs/android/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs). --- ## Where to go next - **Setting up a shard** → [`docs/installer/INSTALL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/installer/INSTALL.md) — the operator guide: what the run asks, where it writes, the patch tier, `doctor` / `update` / `uninstall`, troubleshooting, and the by-hand path. - **Running / customizing the site** → [website README](https://gitea.whitlocktech.com/RunicGateway/website) (tech stack, API endpoints, env vars, security, branding, Swagger at `/api/docs`). - **The Android client** → [Android-app README](https://gitea.whitlocktech.com/RunicGateway/Android-app) and [`docs/android/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs) — the authoritative design contract, milestones, and the push-notification architecture. - **The bridge internals** → [docs](https://gitea.whitlocktech.com/RunicGateway/docs) — design docs, the canonical wire-protocol spec, and the integration guide. - **Working on the sidecar or plugin** → [link README](https://gitea.whitlocktech.com/RunicGateway/link) and [servuo-plugins](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins) — building from source, the loopback protocol, and the compatibility rules.
Runic Gateway · whitlocktech@gmail.com