# Installing Runic Gateway on your shard Operator guide for the **Runic Gateway installer** — the tool that takes a working ServUO installation and connects it to a Runic Gateway website. > **Status: the installer binary is not released yet.** > > Everything it installs *is* released and published — the sidecar, the plugin overlay, and the > [bundle manifest](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/main/bundles/current.json) > that names the checked combination of the two. This guide is the operator-facing contract those > phases build to, and it is written first on purpose: it is the specification of what the run > looks like, what it asks, where it writes, and what it prints. > > **You can install today without it** — [Appendix A](#appendix-a--installing-by-hand) is the same > deployment done by hand, with the commands verified against the current releases. When the binary > ships, Appendix A stays as the reference for what it does under the hood. > > Design of record: [PLAN.md](PLAN.md). --- ## What this installs Three things, on the machine that runs your shard: | # | Component | Where it comes from | |---|---|---| | 1 | **The plugin overlay** — C# source that ServUO compiles at boot, copied into your server tree | [`RunicGateway/servuo-plugins`](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins) release tarball | | 2 | **The uo-link sidecar** — a small Rust service that the shard dials out to, and that your website reads from | [`RunicGateway/link`](https://gitea.whitlocktech.com/RunicGateway/link) release binary | | 3 | **A record of what it did** — `install.json`, plus a cached copy of any patches it applied | Written by the installer | ``` ServUO shard ──loopback TCP 127.0.0.1:7788──► uo-link sidecar ──HTTP + WebSocket──► website (1) overlay (2) binary + service (yours) ``` The shard **dials out**; it never listens for the website and is never reachable from the internet. Only the sidecar is exposed, and only to your website. ### What it deliberately does not do - **It never restarts or manages ServUO.** Your shard keeps starting the way it always has. The installer refuses to run while ServUO is up, and tells you when a restart is required. - **It never deletes anything from your server tree.** The overlay sync only adds and overwrites. - **It never contacts your website.** It prints four values for you to paste into Admin → Shard. - **It never edits stock ServUO files without asking.** That is the opt-in [patch tier](#4-the-patch-tier-optional), and skipping it still leaves you with a working bridge. --- ## Before you begin | Requirement | Detail | |---|---| | A working ServUO install | It must currently boot and compile scripts cleanly. The installer deploys onto a healthy shard; it does not repair a broken one. | | ServUO **57.4** *(patch tier only)* | **57.4 is the only supported version.** The base install works on any reasonably current ServUO. The patch tier is written and tested against stock 57.4; on any other version it is **unsupported and untested** — you can still choose to run it, behind an explicit opt-in, and it applies only where the exact lines it patches are unchanged. See [§4](#4-the-patch-tier-optional). | | ServUO **stopped** | `ServUO.exe` holds a lock on `Scripts.dll` and writes `Saves/` on exit. The installer refuses to deploy under a running shard. | | Administrator / root | It writes into system directories and registers a service. | | Outbound HTTPS | To `gitea.whitlocktech.com`, to fetch the bundle and the two artifacts. Nothing inbound is needed, and no Gitea account or git client is required. | | The sidecar on the **same host** as the shard | The shard connects to `127.0.0.1:7788`. Splitting them is not supported — the loopback socket *is* the trust boundary for inbound commands. | | Admin access to your Runic Gateway site | The last step is pasting four values into Admin → Shard. | **Back up first.** The overlay overwrites `Scripts/Scripts.csproj` (a stock file), and the patch tier edits stock sources. A copy of `Scripts/` and `Config/` before you start costs nothing. --- ## 1. Download and verify Releases are **unsigned**. There is no code-signing certificate and no notarization, so the `SHA256SUMS` file published beside every artifact is the whole trust anchor — check it. Download the installer for your OS, plus `SHA256SUMS`, from the [installer releases page](https://gitea.whitlocktech.com/RunicGateway/installer/releases): ``` runicgateway-installer-linux-x86_64 runicgateway-installer-windows-x86_64.exe SHA256SUMS ``` **Linux** ```bash sha256sum -c SHA256SUMS --ignore-missing chmod +x runicgateway-installer-linux-x86_64 ``` **Windows** (PowerShell) ```powershell (Get-FileHash .\runicgateway-installer-windows-x86_64.exe -Algorithm SHA256).Hash Get-Content .\SHA256SUMS # compare the line for this file, case-insensitively ``` Windows will show a **SmartScreen "Windows protected your PC"** prompt on first run, because the binary is unsigned and unknown. Once you have verified the checksum above: *More info* → *Run anyway*. If you would rather not, Appendix A's manual path uses no unsigned binary except the sidecar itself, which you verify the same way. The installer applies the same standard to everything **it** downloads: each artifact's SHA256 is checked against the value recorded in the bundle manifest — which CI computed after verifying it against the publishing repo's own `SHA256SUMS` — and a mismatch aborts the run. ### What it installs is a bundle, not "latest" The three components version independently but must agree on one wire protocol, so CI publishes a [**bundle**](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/main/bundles/README.md): one exact, protocol-checked pair of sidecar + overlay versions. The installer resolves that at run time rather than hardcoding versions or blindly taking each repo's newest release. Consequences worth knowing: - A sidecar patch release does **not** mean re-downloading the installer. The bundle is data. - `--bundle ` (e.g. `--bundle 2026.08.04`) pins an exact past combination, so a reinstall six months from now reproduces today's install rather than tomorrow's. --- ## 2. Run it ```bash sudo ./runicgateway-installer-linux-x86_64 install ``` ```powershell # Windows: from an elevated PowerShell .\runicgateway-installer-windows-x86_64.exe install ``` Run `install --verify` first if you want to see exactly what would change and write nothing — the same idea as `deploy.ps1 -Verify`, which developers of the plugin use. The installer **does not install itself.** Keep the binary somewhere sensible on the host (it is one file); `doctor`, `update` and `uninstall` are run from it later. Examples below shorten it to `runicgateway`. ### What it asks 1. **Your ServUO root** — detected if the installer is run from inside it or from an obvious sibling, otherwise prompted. A directory qualifies only if it contains `ServUO.exe`, `Scripts/` and `Config/`. 2. **Whether to apply the patch tier** — off unless you say yes. On a ServUO that is not 57.4 the prompt defaults to **no** and carries an unsupported-version warning you have to answer past. See [§4](#4-the-patch-tier-optional). 3. **The hostname your website should use to reach this machine** — used only to compose the two URLs it prints at the end. The sidecar's bind address is frequently `127.0.0.1` or `0.0.0.0`, neither of which is something to hand to a website. 4. **Your site's URL** — used only to print a clickable link to its Admin → Shard page. The installer never contacts your website. ### An illustrative run ``` Runic Gateway installer — bundle 2026.08.04 (protocol 3) ServUO /opt/ServUO (57.4) Shard process not running Overlay servuo-plugins v0.1.1 protocol 3 Sidecar uo-link v1.1.0 protocol 3 ✓ overlay tarball verified sha256 75dc6d6c… ✓ sidecar binary verified sha256 27d491ef… Overlay sync ADD Config/Bridge.cfg ADD Scripts/Custom/Bridge/*.cs (22 files) CHANGE Scripts/Scripts.csproj deployed. add=23 change=1 unchanged=0 Patch tier skipped (not selected) Without it: no vendor.sale events, no in-game moderation audit forwarding. uo-link binary /usr/bin/runicgateway-link config /etc/runicgateway/sidecar.toml (created) database /var/lib/runicgateway/uo-link.db service runicgateway-link.service enabled, running Recorded /etc/runicgateway/install.json Scripts.csproj changed — ServUO rebuilds Scripts.dll on next boot. Start your shard when ready; the installer does not start it for you. ``` Then the [token handoff](#5-connect-the-website). ### Commands and flags The surface this guide specifies. Each command is idempotent: a second run with nothing new to do reports "unchanged" and writes nothing. | Command | What it does | |---|---| | `install` | The full run above. | | `doctor` | Diagnoses an existing deployment end to end — see [§7](#7-day-two). | | `update` | Re-resolves the bundle; updates the sidecar (replace + restart) and the overlay (re-sync + tell you to restart ServUO). | | `uninstall` | Removes only what the installer exclusively owns; prints — never performs — anything inside your ServUO tree. | | Flag | Applies to | Meaning | |---|---|---| | `--verify` | `install`, `update` | Dry run. Report every change that would be made; write nothing. | | `--servuo ` | `install`, `doctor`, `update` | Name the ServUO root instead of detecting or prompting. | | `--bundle ` | `install`, `update` | Pin an exact published bundle instead of the current one. | | `--patches` / `--no-patches` | `install` | Decide the patch tier non-interactively. `--patches` never loosens the region check: patches whose target lines are not stock are reported for you to apply by hand, not forced. | | `--patches-unsupported-servuo` | `install` | Required *in addition to* `--patches` to run the patch tier on a ServUO that is not 57.4. Unsupported and untested — see [§4](#4-the-patch-tier-optional). Ignored on 57.4. | | `--host ` | `install` | The hostname to print in the website URLs. | | `--site-url ` | `install` | Your site's base URL, for the Admin → Shard link. | | `--yes` | all | Assume the default answer to every prompt. Combine with the flags above for an unattended run. | | `--purge` | `uninstall` | Also delete `sidecar.toml` and `uo-link.db`, which are otherwise kept. | --- ## 3. Where everything lands **Linux** | Path | What | |---|---| | `/usr/bin/runicgateway-link` | The sidecar binary | | `/etc/runicgateway/sidecar.toml` | Sidecar config, including the auth token | | `/etc/runicgateway/install.json` | What the installer deployed: versions, commit, per-file hashes, applied patches, timestamps | | `/etc/runicgateway/patches/` | Copies of any patches applied, so `uninstall` can print the exact hunks long after the release tarball is gone | | `/var/lib/runicgateway/uo-link.db` | The sidecar's SQLite store (event history, cached profiles, link map) | | `/etc/systemd/system/runicgateway-link.service` | The service unit, running as a dedicated user | **Windows** | Path | What | |---|---| | `%ProgramFiles%\RunicGateway\uo-link-sidecar.exe` | The sidecar binary | | `%ProgramData%\RunicGateway\sidecar.toml` | Sidecar config, including the auth token | | `%ProgramData%\RunicGateway\install.json` | As above | | `%ProgramData%\RunicGateway\patches\` | As above | | `%ProgramData%\RunicGateway\uo-link.db` | The sidecar's SQLite store | | Service `RunicGatewayLink` | Automatic start, restart on failure | **Inside your ServUO tree** (added by the overlay sync — 24 files): ``` Config/Bridge.cfg every bridge setting, heavily commented Scripts/Custom/Bridge/*.cs 22 files: the plugin itself Scripts/Scripts.csproj OVERWRITES a stock file (see below) ``` Both service definitions pin `UOLINK_CONFIG` and `UOLINK_DB_PATH` explicitly. The sidecar's own defaults are relative to its working directory, and a service manager's working directory is not somewhere you want a database — on Windows it can be `%SystemRoot%\System32` or, under `C:\Program Files\`, a silently redirected VirtualStore copy. > **`Scripts.csproj` is overwritten deliberately.** The stock file omits `Scripts/Custom/`, so the > plugin would sit in the tree and never compile — and ServUO would not tell you, because it > ignores the script build's exit code and silently reloads the previous `Scripts.dll`. That > failure mode is the reason [§6](#6-start-servuo-and-verify) exists. --- ## 4. The patch tier (optional) Most of the plugin ships as **added** files, which is why the base install is a safe file copy. Two features cannot: they need edits to stock ServUO sources, because the events they depend on do not exist. | Patch | Edits | Gives you | Rebuild needed | |---|---|---|---| | `playervendor-sale-eventsink.patch` + `playervendor-sale-gump.patch` | `Server/EventSink.cs`, `Scripts/Gumps/PlayerVendorGumps.cs` | `vendor.sale` events — player-vendor purchases with buyer, owner, price and commission, which is what cheat detection needs | **Core solution rebuild** (`dotnet build ServUO.sln`) — the dynamic script build is not enough | | `commandlogging-event.patch` | `Scripts/Commands/Logging.cs` | In-game moderation actions (`[ban`, `[kick`, `[bcast`) forwarded to the website's moderation log as `admin.audit` | Script build only — a shard restart is enough | Each patch has a companion `.cs` file that is copied **only after** its patch applies, because it references symbols the patch introduces. That is why they are not in the base overlay: shipping them unconditionally would break the build on every unpatched install. How the installer handles it: - **Opt-in.** The base install completes without it, and declining is a supported outcome, not a degraded one. - **Dry-run first, always.** Every patch is checked before anything is applied, and reported per patch. Most real shards are hand-modified; a patch that does not apply is expected, not alarming. - **A modified file is not automatically a refusal.** These patches touch three small regions of three large files. If you have edited `Logging.cs` somewhere else entirely, the installer says so and still applies the patch — it checks whether *the lines the patch edits* are still stock, not whether the whole file is. It applies only where the surrounding lines match the patch exactly and appear exactly once; anything less and it stops and hands you the hunk to apply by hand. It never force-fits a patch by loosening the match. - **All or nothing per feature.** The two vendor-sale patches are one unit and are applied together or not at all — and within a patch, if one hunk cannot be placed safely, none are. - **Your ServUO version is reported, not decisive** — but see the warning below before running this on anything other than 57.4. - **Recorded, and the `.patch` files cached**, so re-runs stay idempotent and `uninstall` can print the exact hunks to revert — along with how each was applied, since a patch placed into a file you had already modified is one to look at more carefully when reverting. ### ⚠ On any ServUO that is not 57.4: unsupported, untested, no guarantees > **Runic Gateway is designed, built and tested against stock ServUO 57.4.** That is the only > supported version. > > On any other version — a newer release, an older one, or a fork — the patch tier is > **UNSUPPORTED, UNTESTED, and NOT GUARANTEED TO WORK.** You may run it. If you do, you are on your > own: it is not covered by support, and a bad outcome may not show up until your shard is live, > because ServUO's script build reports success even when it failed and quietly keeps running the > previous `Scripts.dll`. > > The installer will still refuse to place a patch anywhere the exact lines it edits have changed — > but matching text is not the same as matching behaviour. A hunk can land correctly and still be > wrong for a tree that has diverged around it. > > **Back up your ServUO tree first, and verify your shard boots and compiles afterwards.** Because of that, on a non-57.4 tree the tier is off by default and takes a deliberate yes: - the interactive prompt defaults to **no** and prints the warning above; - `--patches` on its own is **not** enough — an unattended run must also pass `--patches-unsupported-servuo`; - the choice is recorded, and `doctor` keeps showing an unsupported-version row for the life of the install — so whoever looks after this shard next can see it without being told. A run where the tier is selected on a shard that has been worked on looks like this: ``` Patch tier 2 of 3 applied ✓ playervendor-sale-eventsink Server/EventSink.cs stock file ✓ playervendor-sale-gump Scripts/Gumps/PlayerVendorGumps.cs file modified, patched region stock — applied at line 1180 ✗ commandlogging-event Scripts/Commands/Logging.cs patched region has been modified — not applied apply this hunk by hand, then re-run install: /etc/runicgateway/patches/commandlogging-event.patch ⚠ Server/EventSink.cs changed — rebuild the core: dotnet build ServUO.sln Without commandlogging-event: no in-game moderation audit forwarding. ``` If it is skipped or fails, you lose exactly two things — **`vendor.sale` events** and **in-game moderation audit forwarding**. Everything else works. You can apply the patches later by hand (see `patches/README.md` in the tarball) and re-run `install` to record it. --- ## 5. Connect the website The installer ends a successful run by printing the one manual step it cannot do for you: ``` Runic Gateway is installed. One manual step remains — connect the website to this sidecar: Base URL http://shard.example.com:8080 WebSocket URL ws://shard.example.com:8080/ws Protocol version 3 Auth token 4f9c… (also in /etc/runicgateway/sidecar.toml) Paste these into Admin → Shard on your Runic Gateway site: https://your-site.example/admin/shard The token is write-only once saved — the site will never show it back to you. ``` Every value there comes from asking the installed sidecar itself (`--print-config`), not from a log file or a guess, so it cannot drift from what the service actually runs. On your site, sign in as an administrator and open **Admin → Shard (uo-link)**: | Field on the page | Paste | |---|---| | Enable the shard integration | ✔ on | | Base URL (REST) | the **Base URL** line | | WebSocket URL (feed) | the **WebSocket URL** line | | Auth token | the **Auth token** line | | Protocol | the **Protocol version** line (`3`) | Saving restarts the site's ingest client, so the change takes effect immediately. The token is AES-GCM encrypted at rest and **never returned to any client** — losing it means reading it back from `sidecar.toml` on the shard host, not from the website. ### If your website is on a different machine The sidecar binds `127.0.0.1:8080` by default, which is reachable only from the shard host. If your website runs elsewhere, you must widen the bind — and then narrow the access: 1. Set `[web] bind` in `sidecar.toml` to `0.0.0.0:8080` (or a specific LAN address) and restart the service. 2. **Firewall port 8080 to your website's address only.** The auth token is always on, but it travels as a plain bearer token — the sidecar speaks HTTP, not HTTPS. 3. If the two hosts are not on a trusted network, put the sidecar behind a TLS reverse proxy or a VPN/WireGuard link, and give the website the proxied `https://` / `wss://` URLs. The `[shard] bind` line is a different matter: leave it on `127.0.0.1:7788`. That socket accepts *inbound commands* to the game, and being loopback-only is what makes that safe. --- ## 6. Start ServUO and verify Start your shard the way you always do. Then confirm the bridge is actually live — not merely installed. **A successful file copy is not a working bridge**: ServUO shells out to `dotnet build`, prints the output, ignores the exit code, and reloads the existing `Scripts.dll`, so a broken script build looks exactly like a clean boot. **a. Watch the boot output.** You want to see the build succeed *and* the bridge announce itself: ``` Core: Compiling scripts... Build succeeded. [Bridge] enabled=True endpoint=127.0.0.1:7788 queueCap=10000 sweeps(stat=30s decay=60s … ``` If you scrolled past it, force the question: ```bash cd dotnet build Scripts/Scripts.csproj -c Release -p:Platform=x64 # must be 0 errors ``` **b. Ask the shard, in game.** As an Administrator: ``` [bridge status ``` It reports the config plus `connected=True depth=0 sent=… dropped=0 …`. `connected=False` means the shard cannot reach the sidecar; `dropped` climbing means the sidecar is wedged and the shard is shedding events rather than stalling — which it is designed to do. `[bridge reload` re-reads `Bridge.cfg` without a restart; `[bridge sweepnow` forces one pass of every stream. **c. Ask the sidecar.** `/health` needs no auth, so it is safe to curl from a terminal: ```bash curl -s http://127.0.0.1:8080/health {"status":"ok","protocol":3,"plugin_connected":true,"database":"ok","uptime":"2m","last_event":"2026-08-04T18:22:10.412Z"} ``` `plugin_connected: true` is the one that matters — it is the only value in this whole guide that distinguishes "files copied" from "the bridge works". **d. Ask the website.** The public site should stop showing the shard as offline, and live events should appear on the admin dashboard within seconds. --- ## 7. Day two ### `runicgateway doctor` The command that makes this supportable. Run it before asking anyone for help — its output is the first thing a maintainer will want. ``` ✓ ServUO found /opt/ServUO (57.4) ✓ Overlay in sync 24 files, all hashes match install.json ⚠ Patch tier 1 of 3 applied (region-match) — vendor.sale unavailable ✓ uo-link installed 1.1.0 ✓ Service running, enabled ✓ Sidecar reachable 127.0.0.1:8080 /health ok ✓ Protocol sidecar 3 = overlay manifest 3 ✗ Shard connected no shard has dialed in since boot ``` Three of those rows come from asking the installed sidecar (`--version`, `--print-config`) rather than from reading `install.json`, so `doctor` reports what the binary would actually do — including which config and database file the *service* resolves — rather than what the installer believes it was told. The overlay row compares live file hashes against both `install.json` and the release manifest, which is how it tells "you edited a deployed file" from "the overlay moved on". ### `runicgateway update` Re-resolves the bundle and moves both halves to a combination whose protocol versions were checked together — never to two independently-latest artifacts that may disagree. - **Sidecar**: download → verify → replace binary → restart service. No shard downtime. - **Overlay**: download → verify → re-sync → record the new commit → **tell you to restart ServUO.** It does not restart your shard. Your `sidecar.toml`, your `Bridge.cfg` edits and your database are not touched. `Bridge.cfg` is overwritten only if you have not changed it; a modified copy is reported, not clobbered. ### `runicgateway uninstall` Removes what it exclusively owns, and **prints** everything else. The installer cannot know what you have changed in your own server tree since deployment, so an automatic revert risks silently eating your work. | | | |---|---| | **Removed** | The sidecar binary, its service entry, `install.json`, the cached patch set | | **Kept** | `sidecar.toml` and `uo-link.db` — config and history survive (`--purge` drops them) | | **Printed, not done** | Every overlay file deployed into your ServUO tree, by path, for you to delete | | **Printed, not done** | The exact hunks each applied patch added to `EventSink.cs`, `PlayerVendorGumps.cs` and `Logging.cs`, for you to revert | The report is also written to a file, so it survives the scrollback. --- ## Troubleshooting | Symptom | Cause and fix | |---|---| | **"ServUO is running — stop it before installing"** | Correct, and not overridable. `ServUO.exe` locks `Scripts.dll` and rewrites `Saves/` on exit; deploying underneath it corrupts one or both. Stop the shard, install, start it again. | | Shard boots clean but nothing reaches the site | The classic silent failure: ServUO ignores the script build's exit code and reloaded a **stale `Scripts.dll`**. Run `dotnet build Scripts/Scripts.csproj -c Release -p:Platform=x64` and read the errors it prints. | | `[bridge status` says `connected=False` | The sidecar is not listening on `127.0.0.1:7788`. Check the service is running, and that `[shard] bind` in `sidecar.toml` matches `Host`/`Port` in `Bridge.cfg`. | | `[bridge` is not a command | The plugin did not compile, or `Bridge.cfg` has the bridge disabled. See the row above. | | Website says the shard is offline; `/health` is fine locally | The website cannot reach port 8080 — bind address or firewall. See [§5](#if-your-website-is-on-a-different-machine). Note that the site is *designed* to render normally with the shard offline, so this fails quietly by design. | | Website logs `409` from the sidecar | Protocol mismatch: the number in Admin → Shard does not match the sidecar's. The sidecar rejects rather than mis-parsing. Set the field to what `/health` reports (`protocol`). If the *sidecar* and *overlay* disagree, you have a hand-assembled pair — reinstall from a bundle. | | `401` from the sidecar | Wrong or missing auth token. Read the live one back with `uo-link-sidecar --print-config --config `; do not retype it from a screenshot. | | A patch will not apply | Expected on a hand-modified shard. The base install is unaffected; you lose only the two features in [§4](#4-the-patch-tier-optional). Apply the hunks by hand if you want them. | | `vendor.sale` events never arrive despite patching | The `EventSink.cs` patch is a **core** change. A shard restart is not enough — rebuild the solution (`dotnet build ServUO.sln`). | | Sidecar writes its database somewhere unexpected | A relative `[store] path` resolves against the directory holding `sidecar.toml` — not the working directory. Run `--print-config` to see the absolute path it will actually use. | | Token leaked into a log or a screenshot | Clear `[web] auth_token` in `sidecar.toml`, restart the service (a new token is generated and saved), read it back with `--print-config`, and re-save it in Admin → Shard. | --- ## Appendix A — installing by hand This is what the installer automates. It works today, on the current releases, and is the fallback whenever you would rather not run an unsigned binary. Throughout: `` is your ServUO root, and **the shard is stopped**. ### A1. Fetch the bundle (so you install a checked pair) ```bash curl -s https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/main/bundles/current.json ``` It names the sidecar tag, the overlay tag, their agreed `protocol`, and the SHA256 of every asset. Use those versions together; that pairing is the only thing CI has verified. ### A2. Deploy the plugin overlay ```bash curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v0.1.1/runicgateway-overlay-0.1.1.tar.gz curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v0.1.1/SHA256SUMS sha256sum -c SHA256SUMS --ignore-missing # must say: OK tar xzf runicgateway-overlay-0.1.1.tar.gz # → runicgateway-overlay/ cd runicgateway-overlay cat manifest.json # version, commit, protocol, per-file hashes cp -r overlay/. / # adds files; overwrites Scripts/Scripts.csproj ``` On Windows, `Expand-Archive` does not read `.tar.gz`; use `tar.exe` (shipped with Windows 10+) and `Copy-Item -Recurse -Force`. Plugin developers have `deploy.ps1` in the source repo, which does the same copy with a hash diff and a `-Verify` dry run — it is not shipped in the tarball. The overlay only ever **adds or overwrites**. Nothing in your tree is deleted. *Optional — the patch tier* (stock ServUO 57.4 only; see `patches/README.md` in the tarball for the full explanation): ```bash cd git apply --check patches/playervendor-sale-eventsink.patch patches/playervendor-sale-gump.patch git apply patches/playervendor-sale-eventsink.patch patches/playervendor-sale-gump.patch cp patches/BridgeVendorSale.cs Scripts/Custom/Bridge/ dotnet build ServUO.sln # REQUIRED — EventSink.cs is a core file git apply --check patches/commandlogging-event.patch git apply patches/commandlogging-event.patch cp patches/BridgeModerationAudit.cs Scripts/Custom/Bridge/ ``` `git apply` works in a plain directory — the shard does not need to be a git repo. If you use `patch` instead, note the core files are CRLF: use `patch --binary`. ### A3. Install the sidecar ```bash curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/uo-link-sidecar-linux-x86_64 curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/SHA256SUMS sha256sum -c SHA256SUMS --ignore-missing sudo install -m 0755 uo-link-sidecar-linux-x86_64 /usr/bin/runicgateway-link sudo mkdir -p /etc/runicgateway /var/lib/runicgateway ``` Provision the config and read back the token in one step. `--print-config` writes the file if it is missing, generates the auth token if there is none, and prints the resolved settings as JSON — it is the supported alternative to scraping the startup log: ```bash sudo UOLINK_DB_PATH=/var/lib/runicgateway/uo-link.db \ /usr/bin/runicgateway-link --print-config --config /etc/runicgateway/sidecar.toml ``` ```json { "component": "uo-link-sidecar", "version": "1.1.0", "protocol": 3, "config_path": "/etc/runicgateway/sidecar.toml", "config_created": true, "token_generated": true, "shard": { "bind": "127.0.0.1:7788" }, "web": { "bind": "127.0.0.1:8080", "ws_path": "/ws", "auth_required": true, "auth_token": "4f9c…" }, "store": { "path": "/var/lib/runicgateway/uo-link.db" } } ``` `config_created` and `token_generated` tell you whether *this* run provisioned anything — the values alone cannot distinguish a fresh install from a re-read. **The output contains the auth token in clear text**: keep it out of shell transcripts, logs and support bundles. ### A4. Register the service **Linux** — `/etc/systemd/system/runicgateway-link.service`: ```ini [Unit] Description=Runic Gateway uo-link sidecar After=network.target [Service] Type=simple User=runicgateway Environment=UOLINK_CONFIG=/etc/runicgateway/sidecar.toml Environment=UOLINK_DB_PATH=/var/lib/runicgateway/uo-link.db ExecStart=/usr/bin/runicgateway-link Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target ``` ```bash sudo useradd --system --no-create-home runicgateway sudo chown -R runicgateway /var/lib/runicgateway /etc/runicgateway sudo systemctl daemon-reload sudo systemctl enable --now runicgateway-link systemctl status runicgateway-link ``` **Windows** (elevated PowerShell) — binary under `%ProgramFiles%`, data under `%ProgramData%`: ```powershell New-Item -ItemType Directory -Force "$env:ProgramFiles\RunicGateway", "$env:ProgramData\RunicGateway" | Out-Null Copy-Item .\uo-link-sidecar-windows-x86_64.exe "$env:ProgramFiles\RunicGateway\uo-link-sidecar.exe" & "$env:ProgramFiles\RunicGateway\uo-link-sidecar.exe" --print-config --config "$env:ProgramData\RunicGateway\sidecar.toml" sc.exe create RunicGatewayLink binPath= "\"$env:ProgramFiles\RunicGateway\uo-link-sidecar.exe\"" start= auto sc.exe failure RunicGatewayLink reset= 86400 actions= restart/5000 [Environment]::SetEnvironmentVariable('UOLINK_CONFIG', "$env:ProgramData\RunicGateway\sidecar.toml", 'Machine') [Environment]::SetEnvironmentVariable('UOLINK_DB_PATH', "$env:ProgramData\RunicGateway\uo-link.db", 'Machine') sc.exe start RunicGatewayLink ``` Machine environment variables are read at service start, so set them before starting — and never leave the config path to the default, which is relative to the service's working directory. ### A5. Connect the website, start the shard, verify Exactly as in [§5](#5-connect-the-website) and [§6](#6-start-servuo-and-verify): paste the four values into Admin → Shard, start ServUO, then check `[bridge status` in game and `/health` on the sidecar. ### A6. Updating by hand Re-read `current.json`, and if either version moved: replace the sidecar binary and restart its service; re-extract the overlay tarball over your tree and restart ServUO. Keep the two in step — `current.json` is the only statement that a given pair speaks the same protocol. --- ## Appendix B — `sidecar.toml` reference Written on first run with a generated token. Environment variables override the file; the file overrides these defaults. ```toml [shard] bind = "127.0.0.1:7788" # where the SHARD dials in. Keep this on loopback. [web] bind = "127.0.0.1:8080" # where the WEBSITE connects. Widen only with a firewall in front. auth_token = "…" # generated if blank; the website's Admin → Shard "Auth token" [store] path = "uo-link.db" # relative paths resolve against this file's directory, not the CWD ``` | Environment variable | Overrides | |---|---| | `UOLINK_CONFIG` | Which config file to read (`--config ` outranks it) | | `UOLINK_SHARD_BIND` | `[shard] bind` | | `UOLINK_WEB_BIND` | `[web] bind` | | `UOLINK_WEB_TOKEN` | `[web] auth_token` | | `UOLINK_DB_PATH` | `[store] path` | | Sidecar command | Output | |---|---| | `uo-link-sidecar --version` | `uo-link-sidecar 1.1.0 (protocol 3)` | | `uo-link-sidecar --print-config [--config PATH]` | The JSON in [A3](#a3-install-the-sidecar). Provisions on first run. **Contains the token.** | | `uo-link-sidecar --help` | Usage. An unrecognized argument exits `2` rather than starting a sidecar you did not ask for. | Authentication is **always on**: a blank token is generated and written back, so the web surface is never unauthenticated. `/health` is the one unauthenticated route, so monitoring can reach it. --- ## Appendix C — `Config/Bridge.cfg` settings worth reviewing The file is deployed heavily commented and every setting has a working default — you can leave it entirely alone. These are the ones most shards want to look at once. Run `[bridge reload` after editing; endpoint changes take effect on the next reconnect. | Setting | Default | Why you might change it | |---|---|---| | `LinkUrl` | `https://yoursite/link` | Shown in game when a player runs `[link` to connect their account. **Set this to your site.** | | `PublicConnectAddress` | *(blank)* | The one connection detail the bridge will publish, e.g. `play.myshard.com,2593`. Blank omits it; `Server.cfg`'s address is **never** published automatically. | | `AdminWriteEnabled` | `false` | Opt-in staff write plane: kick/ban/broadcast from the website. Authorization is enforced on the website; `AdminAccessFloor` is the shard-side floor that even a compromised sidecar cannot cross. | | `MarketEnabled`, `MarketSweepSeconds`, `MarketSweepBatch` | `true`, `60`, `25` | The player-vendor index. Coverage takes `ceil(vendors / batch) × seconds` — 500 vendors is one full pass every 20 minutes at the defaults. | | `PointsLeaderboardEnabled`, `PointsTopN`, `PointsSystems` | `true`, `10`, *(all shown on the loyalty gump)* | Standings boards. One frame **per system**, and ServUO carries ~25 of them, so a large `TopN` multiplies. | | `RulesetEnabled`, `RulesetIncludeSchedule` | `true`, `true` | Publishes your ruleset (expansion, caps, systems on/off) to the site's rules page. Turn the schedule off if you would rather not advertise a predictable restart window. | | `SignupMode` | `hybrid` | Which side may mint accounts — `website`, `game`, or `hybrid`. Pair `website` with `Accounts.AutoCreateAccounts=false`, or an in-game login still creates accounts. | | `QueueCap` | `10000` | Outbound queue cap. On overflow the plugin **drops oldest** and counts drops, because a stalled sidecar must never take the shard down with it. | Sweep intervals (`StatSweepSeconds`, `DecaySweepSeconds`, `EconomySweepSeconds`, and the rest) trade freshness against Core-thread time. The measured cost is small — a vitals sweep is 0.0015 ms per character, so 1000 online players is ~1.5 ms per pass — but there is rarely anything to gain by hurrying them. --- ## Where to go next | Doc | What | |---|---| | [PLAN.md](PLAN.md) | The installer's design of record — phases, locked decisions, the bundle model | | [`bundles/README.md`](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/main/bundles/README.md) | The compat matrix: what a bundle is and how it is composed | | [link/INTEGRATION.md](../link/INTEGRATION.md) | The sidecar's HTTP/WS API — for anyone integrating something other than the website | | [link/ADMIN_CONTROLS.md](../link/ADMIN_CONTROLS.md) | The staff write plane in detail, before you turn `AdminWriteEnabled` on | | [website/SHARD_VISIBILITY.md](../website/SHARD_VISIBILITY.md) | Which shard data each audience sees, configured on the website | | [link/SHARD_PREREQS.md](../link/SHARD_PREREQS.md) | A worked example of diagnosing a shard whose scripts silently stopped compiling |