Compare commits
1 Commits
chore/sync
...
docs/insta
| Author | SHA1 | Date | |
|---|---|---|---|
| 152ffef86e |
15
README.md
15
README.md
@@ -7,11 +7,10 @@ so they live in one place, independent of either codebase.
|
||||
## Layout
|
||||
|
||||
```
|
||||
website/ docs from the shard website (Node/Express + MariaDB + React/Vite)
|
||||
link/ docs from the ServUO bridge (C# plugin + Rust sidecar + Node WS)
|
||||
android/ docs from the native Android client (Kotlin + Jetpack Compose)
|
||||
installer/ docs for the installer that deploys a shard's bridge components
|
||||
ci/ cross-cutting CI/quality notes
|
||||
website/ docs from the shard website (Node/Express + MariaDB + React/Vite)
|
||||
link/ docs from the ServUO bridge (C# plugin + Rust sidecar + Node WS)
|
||||
android/ docs from the native Android client (Kotlin + Jetpack Compose)
|
||||
ci/ cross-cutting CI/quality notes
|
||||
```
|
||||
|
||||
### `website/`
|
||||
@@ -51,12 +50,6 @@ ci/ cross-cutting CI/quality notes
|
||||
| [TRUSTED_DEVICES_APP_HANDOFF.md](android/TRUSTED_DEVICES_APP_HANDOFF.md) | Trusted-devices app handoff notes |
|
||||
| [PROJECT_TREE.md](android/PROJECT_TREE.md) | Auto-generated snapshot of the repo's tracked file layout |
|
||||
|
||||
### `installer/`
|
||||
| Doc | What it covers |
|
||||
|---|---|
|
||||
| [INSTALL.md](installer/INSTALL.md) | **Operator guide** — installing Runic Gateway on a ServUO shard, connecting it to the website, and diagnosing it. Includes the by-hand path, which works today |
|
||||
| [PLAN.md](installer/PLAN.md) | Installer design of record — phases, locked decisions, the bundle/compat-matrix model |
|
||||
|
||||
## Provenance
|
||||
|
||||
- `website/*` was extracted from `RunicGateway/website` via `git filter-repo`.
|
||||
|
||||
@@ -1,692 +0,0 @@
|
||||
# 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)* | The base install works on any reasonably current ServUO. The patch tier is verified against stock 57.4 only, and is skipped with a warning on anything else. |
|
||||
| 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 <tag>` (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, and not offered at all if your
|
||||
ServUO is not 57.4. 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 <path>` | `install`, `doctor`, `update` | Name the ServUO root instead of detecting or prompting. |
|
||||
| `--bundle <tag>` | `install`, `update` | Pin an exact published bundle instead of the current one. |
|
||||
| `--patches` / `--no-patches` | `install` | Decide the patch tier non-interactively. `--patches` still refuses on a non-57.4 tree. |
|
||||
| `--host <name>` | `install` | The hostname to print in the website URLs. |
|
||||
| `--site-url <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 (`git apply --check`) before anything is
|
||||
applied, and reported per patch. Most real shards are hand-modified; a patch that does not apply
|
||||
is expected, not alarming.
|
||||
- **All or nothing per feature.** The two vendor-sale patches are one unit and are applied together
|
||||
or not at all.
|
||||
- **Skipped entirely on a ServUO that is not 57.4**, with a warning. Unverified diffs are never
|
||||
applied to an unknown tree.
|
||||
- **Recorded, and the `.patch` files cached**, so re-runs stay idempotent and `uninstall` can print
|
||||
the exact hunks to revert.
|
||||
|
||||
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 <servuo root>
|
||||
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 — 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 <path>`; 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: `<servuo>` 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/. <servuo>/ # 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 <servuo>
|
||||
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 <PATH>` 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 |
|
||||
@@ -1,20 +1,8 @@
|
||||
# Runic Gateway Installer — plan
|
||||
|
||||
Status: **Phase 0 complete.** Every prerequisite in another repo has landed, the installer repo
|
||||
publishes the bundle manifest, and [`INSTALL.md`](INSTALL.md) now specifies the operator-facing run
|
||||
— so *what* the installer installs and *what using it looks like* both exist ahead of the binary.
|
||||
No installer code exists yet; **Phase 1 is next.** This document is the design of record; it
|
||||
Status: **planning**. No installer code exists yet. This document is the design of record; it
|
||||
supersedes the informal overview it grew out of, which described a ServUO integration that does not
|
||||
match how `servuo-plugins` actually ships (see
|
||||
[Corrections](#corrections-to-the-original-overview)).
|
||||
|
||||
| Phase 0 item | State |
|
||||
|---|---|
|
||||
| 0.1 `servuo-plugins` release workflow | ✅ Merged — [servuo-plugins#7](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/pulls/7) + [#8](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/pulls/8); first overlay release is [`v0.1.1`](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/tag/v0.1.1) |
|
||||
| 0.2 `link` installable (data paths + `--print-config`) | ✅ Merged — [link#24](https://gitea.whitlocktech.com/RunicGateway/link/pulls/24) (docs half [docs#84](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/84)); released as [`v1.1.0`](https://gitea.whitlocktech.com/RunicGateway/link/releases/tag/v1.1.0) |
|
||||
| 0.3 Bundle CI in the installer repo | ✅ Merged — [installer#3](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/3), plus the dispatch step in each component ([link#25](https://gitea.whitlocktech.com/RunicGateway/link/pulls/25), [servuo-plugins#9](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/pulls/9)). First bundle: [`2026.08.04`](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/main/bundles/current.json) |
|
||||
| 0.4 This file + `INSTALL.md` | 🟨 In review — [docs#87](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/87). [`INSTALL.md`](INSTALL.md) is the operator guide, written before the binary because it *is* the specification of the run |
|
||||
| — Repo bootstrap (governance + CI) | ✅ [`RunicGateway/installer`](https://gitea.whitlocktech.com/RunicGateway/installer) created; workflows merged ([installer#1](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/1), [#2](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/2)) |
|
||||
match how `servuo-plugins` actually ships (see [Corrections](#corrections-to-the-original-overview)).
|
||||
|
||||
---
|
||||
|
||||
@@ -117,13 +105,9 @@ Two files, two owners:
|
||||
| `/etc/runicgateway/sidecar.toml` | uo-link | The sidecar's own schema, unchanged. Service sets `UOLINK_CONFIG` to this path |
|
||||
| `/etc/runicgateway/install.json` | installer | Deployed versions, file hashes, applied patches, ServUO path, timestamps |
|
||||
|
||||
**Working-directory trap:** the sidecar wrote both `sidecar.toml` and `uo-link.db` relative to CWD.
|
||||
Under `C:\Program Files\` that fails or silently lands in VirtualStore. Phase 0.2 fixed the second
|
||||
half in the sidecar — a relative `[store].path` now resolves against the directory holding
|
||||
`sidecar.toml`, so pinning the config alone is enough to put the database somewhere deterministic —
|
||||
but the config path itself is still CWD-relative by default, and "deterministic" is not the same as
|
||||
"where this install wants it". The service definitions therefore still pin `UOLINK_CONFIG` and
|
||||
`UOLINK_DB_PATH` explicitly:
|
||||
**Working-directory trap:** the sidecar writes both `sidecar.toml` and `uo-link.db` relative to CWD.
|
||||
Under `C:\Program Files\` that fails or silently lands in VirtualStore. The service definitions must
|
||||
pin `UOLINK_CONFIG` and `UOLINK_DB_PATH` explicitly:
|
||||
|
||||
- Linux: config `/etc/runicgateway/sidecar.toml`, db `/var/lib/runicgateway/uo-link.db`, dedicated
|
||||
service user
|
||||
@@ -138,12 +122,6 @@ becomes write-only. This is the largest "I installed it and nothing happened" fa
|
||||
|
||||
The installer closes it by printing a copy-paste block at the end of a successful run — see §6.
|
||||
|
||||
Phase 0.2 supplied the missing half of that: `uo-link-sidecar --print-config` provisions the config
|
||||
if absent and prints the resolved settings — token, both binds, `ws_path`, protocol version, db
|
||||
path — as JSON. The installer reads the block it prints out of that one call. **It never parses the
|
||||
log**, which was the alternative and would have made the handoff depend on a log format that is not
|
||||
a contract.
|
||||
|
||||
### 2.5 `deploy.ps1` cannot be the cross-platform deployer
|
||||
|
||||
It is PowerShell-only; a Linux ServUO host running .NET typically has no `pwsh`. It also hard-throws
|
||||
@@ -156,16 +134,15 @@ operators.
|
||||
|
||||
### 2.6 Prerequisites the overview assumed away
|
||||
|
||||
- **`servuo-plugins` had no release workflow.** Only `link` did. "Pull latest repository" is replaced
|
||||
by a release tarball, which had to be built first — Phase 0 item 1, now in review
|
||||
([servuo-plugins#7](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/pulls/7)).
|
||||
- **`servuo-plugins` has no release workflow.** Only `link` does. "Pull latest repository" is
|
||||
replaced by a release tarball, which has to be built first (Phase 0).
|
||||
- **arm64 is not buildable today.** `link/release.yml` cross-compiles only
|
||||
`x86_64-unknown-linux-gnu` and `x86_64-pc-windows-gnu`. An arm64 `.deb` needs another cross
|
||||
toolchain.
|
||||
- **The compat matrix has no home.** `PROTOCOL_VERSION` lives in `link/sidecar/src/main.rs`. The
|
||||
sidecar publishes it via `X-UOLink-Version` and `/health`, and the website stores an expected
|
||||
value — but the *plugin's* protocol version is not queryable before boot. Phase 0 item 1 gives it
|
||||
a home: `servuo-plugins/overlay.toml`, declared into the overlay manifest. See §7.0 / §7.1.
|
||||
- **The compat matrix has no home.** `PROTOCOL_VERSION` currently lives only in
|
||||
`link/sidecar/src/main.rs`. The sidecar publishes it via `X-UOLink-Version` and `/health`, and the
|
||||
website stores an expected value — but the *plugin's* protocol version is not queryable before
|
||||
boot. See §7.
|
||||
|
||||
---
|
||||
|
||||
@@ -180,7 +157,7 @@ Runic Gateway Installer v1.0.0
|
||||
├── runicgateway-installer-linux-x86_64
|
||||
└── SHA256SUMS
|
||||
|
||||
uo-link v1.1.0 (existing release, extended)
|
||||
uo-link v3.x.y (existing release, extended)
|
||||
├── uo-link-sidecar-windows-x86_64.exe
|
||||
├── uo-link-sidecar-linux-x86_64
|
||||
├── runicgateway-link_<ver>_amd64.deb (Phase 5)
|
||||
@@ -245,140 +222,22 @@ Repo work that must land before an installer can exist.
|
||||
consume only `{version, changelog, artifacts}`). The adapter half produces
|
||||
`runicgateway-overlay-<ver>.tar.gz` containing `overlay/`, `patches/`, and a `manifest.json`
|
||||
(version, commit, per-file SHA256, declared protocol version, minimum ServUO version).
|
||||
|
||||
As built ([servuo-plugins#7](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/pulls/7)),
|
||||
with three deviations from `link`'s copy that each fell out of the repo rather than being chosen:
|
||||
|
||||
- **No build gates, structural gates instead.** Nothing in that repo can be compiled without
|
||||
ServUO reference assemblies, so CI asserts what it honestly can: `Bridge.cfg` and the Bridge
|
||||
scripts present, `Scripts.csproj` present (its absence ships code that never compiles while
|
||||
ServUO reports success — §2.1), every `.patch` parseable via `git apply --stat`, and each
|
||||
patch's companion `.cs` present.
|
||||
- **No bump commit, so no push to `main`.** `link` writes the version into `Cargo.toml` because
|
||||
the binary embeds it; the tarball embeds nothing but the generated manifest, so the tag *is*
|
||||
the version. That workflow needs no branch-protection exception.
|
||||
- **`overlay.toml` at the repo root** holds the declared `protocol` and the ServUO compatibility
|
||||
values, read by CI into the manifest. It exists because the number needs one maintained home —
|
||||
see §7 for why the plugin cannot simply be asked.
|
||||
|
||||
The tarball uses a **fixed** top-level directory, `runicgateway-overlay/`, not a versioned one:
|
||||
the installer looks for `overlay/`, `patches/` and `manifest.json` at known paths rather than
|
||||
parsing the version it is trying to read. Member order, mtime and ownership are pinned, so a
|
||||
given tree yields a byte-identical tarball and its checksum moves only when its contents do.
|
||||
2. **`link`: make the sidecar installable.** Confirm/settle default data paths, and add a way to
|
||||
read back config non-interactively (e.g. `--print-config` emitting JSON: bind addresses, token,
|
||||
protocol version, db path) so the installer does not have to scrape logs for the token.
|
||||
|
||||
As built ([link#24](https://gitea.whitlocktech.com/RunicGateway/link/pulls/24)) — the sidecar
|
||||
had **no CLI at all** before this, so the shape was chosen rather than inherited:
|
||||
|
||||
- **Four flags, hand-rolled:** `--print-config`, `--config <PATH>`, `--version`, `--help`. No
|
||||
argument-parsing crate — it would be larger than the code it replaced — and deliberately no
|
||||
flags that duplicate a config key, so `sidecar.toml` stays the single place settings live.
|
||||
An unrecognized argument exits `2`; silently ignoring a typo'd flag would start a sidecar that
|
||||
is not the one the installer asked for.
|
||||
- **`--print-config` performs first-run setup rather than only reporting.** It runs the same
|
||||
load path a normal start does, so a missing config file is written and a blank token is
|
||||
generated and saved. That collapses "provision the sidecar" and "find out its token" into one
|
||||
non-interactive call — which is exactly the sequence §6 needs. `config_created` and
|
||||
`token_generated` say whether *this* run did either, because the values alone cannot
|
||||
distinguish a fresh install from a re-read of an existing one, and a re-run must not report a
|
||||
token as newly minted.
|
||||
- **The document is the whole of stdout.** The log subscriber writes to stdout, so it is not
|
||||
started in this mode. `ws_path` is emitted from the same constant the route is registered
|
||||
with, so the installer's WebSocket URL cannot drift from the server's.
|
||||
- **Relative `[store].path` now anchors to the config file's directory, not the CWD** — see
|
||||
§2.3, which this half-closes on the sidecar side. Absolute paths are used as written; parent
|
||||
directories are created; `:memory:` and `file:` URIs are left alone.
|
||||
- **The db path is handed to sqlx as a path, not a `sqlite://` URL.** The URL spelling is
|
||||
parsed as one: it percent-decodes the path and splits it on `?`, so an installed path
|
||||
containing `%20` opened a different file than the operator named.
|
||||
- **No platform data directories are compiled in.** That is the "settle" half of this item, and
|
||||
the answer is that the *installer* owns layout (§2.3) and pins `UOLINK_CONFIG` /
|
||||
`UOLINK_DB_PATH` in the service definition. Baking `/etc` and `%ProgramData%` defaults into
|
||||
the binary would give the same paths two owners and break `cargo run` in a working tree.
|
||||
3. **Bundle CI in the installer repo** (§7). Compose job (read both repos' latest releases → run the
|
||||
two gates → publish `bundle.json`), the nightly cron, and the dispatch step appended to each
|
||||
component's release workflow. This must exist before Phase 1 is useful, since the installer
|
||||
resolves what to install *from* the bundle.
|
||||
|
||||
As built ([installer#3](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/3),
|
||||
[link#25](https://gitea.whitlocktech.com/RunicGateway/link/pulls/25),
|
||||
[servuo-plugins#9](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/pulls/9)) —
|
||||
`installer/.gitea/workflows/bundle.yml`, with the decisions §7 had left open:
|
||||
|
||||
- **Bundles are committed to the installer repo, not published as releases** — see §7.1 for
|
||||
where and why. That was the one genuinely open question here, and the deciding factor is that
|
||||
this repo's *own* releases are the installer binaries.
|
||||
- **Gate 1 reads the sidecar's protocol from source at the release tag**, not from the binary.
|
||||
`--print-config` (Phase 0.2) would answer authoritatively, but only for releases from `v1.1.0`
|
||||
onward, and `--bundle <tag>` has to be able to recompose a bundle from an older pair. Reading
|
||||
`sidecar/src/main.rs` at the tag the release was built from works uniformly, needs no execution
|
||||
of a downloaded artifact, and does not provision a throwaway config whose auth token would then
|
||||
be sitting in a CI log. A constant that has moved or been renamed is a hard failure — treating
|
||||
"could not read" as "matches" is exactly how a mismatched pair would ship.
|
||||
- **Gate 2 records the hash CI computed itself**, after verifying the download against the
|
||||
publishing repo's `SHA256SUMS`. It also asserts the reverse direction — an asset with *no*
|
||||
`SHA256SUMS` entry — because `sha256sum -c` silently passes over a file the sums file does not
|
||||
mention, which would put an unverified artifact in the bundle.
|
||||
- **Release metadata is read anonymously**, on purpose: those are exactly the requests the
|
||||
shipped installer makes on a host with no Gitea credentials, so a repo flipped to private
|
||||
fails CI here instead of on an operator's machine.
|
||||
- **An unrecognized asset name is a hard failure.** link's binaries are mapped onto platform keys
|
||||
by suffix; adding a target (aarch64, macOS) to its release workflow therefore reddens this job
|
||||
rather than silently omitting the new binary from every bundle.
|
||||
- **A run that changes nothing writes nothing** — the comparison excludes `bundle` and
|
||||
`generated`, which are metadata about the run. Without that the nightly cron would commit a
|
||||
dated duplicate of the same matrix every morning.
|
||||
|
||||
The workflow's compose steps were run against the live releases before merge, producing the
|
||||
first bundle (`2026.08.04`: link `v1.1.0` + overlay `v0.1.1`, protocol 3), which is committed so
|
||||
the manifest exists ahead of the binary that reads it.
|
||||
4. **`docs`: this file, plus `docs/installer/INSTALL.md`** (the operator-facing guide) once the
|
||||
shape is settled.
|
||||
|
||||
As built ([`INSTALL.md`](INSTALL.md)) — written *before* the binary on purpose. Everything it
|
||||
installs is already released (items 1–3), so the guide is not speculation about a tool that
|
||||
might exist; it is the specification of what the run asks, where it writes, what it prints, and
|
||||
what the operator does next. Phase 1–4 implement it.
|
||||
|
||||
- **It is useful before the installer exists.** Appendix A is the same deployment done by hand —
|
||||
bundle fetch, tarball verify + overlay copy, the optional patch tier, `--print-config`
|
||||
provisioning, and a systemd unit / `sc create` service — composed from the released artifacts'
|
||||
actual contents and the sidecar's config and CLI source rather than from memory. That appendix
|
||||
doubles as **Phase 1's acceptance test**: walking it end to end on a real shard is what proves
|
||||
the automated path has nothing left to discover.
|
||||
- **The installer does not install itself.** §5's `runicgateway doctor` sketch implied a name on
|
||||
`PATH`; nothing places one there, and adding self-installation would give the tool a second
|
||||
lifecycle to manage. The guide names the downloaded artifact, says to keep it, and shortens it
|
||||
in later examples.
|
||||
- **The flag surface got fixed here**, because a guide cannot describe a run in the abstract:
|
||||
`--verify`, `--bundle`, `--purge` were already named by §5/§7; `--servuo`, `--patches` /
|
||||
`--no-patches`, `--host`, `--site-url` and `--yes` are the remainder, chosen so every prompt
|
||||
in §6's handoff has a non-interactive equivalent and an unattended install is expressible.
|
||||
- **A modified `Bridge.cfg` must survive an update** — see Phase 1, where this changes the sync
|
||||
rule inherited from `deploy.ps1`.
|
||||
- **Remote-website deployments needed an answer.** `[web] bind` defaults to `127.0.0.1`, which
|
||||
only works when the site runs on the shard host. The guide says to widen it, firewall it to
|
||||
the website's address, and front it with TLS or a VPN off a trusted network — because the
|
||||
token is always required but travels as a plain bearer token over HTTP. `[shard] bind` stays
|
||||
on loopback, since that socket carries inbound commands *into* the game.
|
||||
|
||||
### Phase 1 — installer core
|
||||
|
||||
- ServUO root detection and validation (`ServUO.exe`, `Scripts/`, `Config/`), with version detection
|
||||
and an explicit refusal when the ServUO process is running.
|
||||
- Overlay sync: fetch tarball → verify SHA256 → hash-compare against the server tree → add/change,
|
||||
**never delete**. Port of `deploy.ps1` semantics including its `-Verify` dry run (`--verify`).
|
||||
- **One deviation from `deploy.ps1`: an operator-modified `Config/Bridge.cfg` is reported, not
|
||||
overwritten.** `deploy.ps1` overwrites every file whose hash differs, which is right for a
|
||||
developer redeploying their own tree and wrong for an operator who has set `LinkUrl`,
|
||||
`PublicConnectAddress` and sweep intervals — an `update` would silently revert the shard's entire
|
||||
configuration. `install.json` records the hash deployed, so the installer can distinguish "the
|
||||
operator edited this" from "the overlay moved on" (§7.0) and act only on the second. The rule is
|
||||
specific to `Bridge.cfg`: it is the only file in the overlay that is *meant* to be edited in
|
||||
place, and it carries no code, so a stale copy cannot break the build. Every `.cs` file and
|
||||
`Scripts.csproj` still overwrite unconditionally.
|
||||
- Write `install.json`: component, version, source commit, per-file hashes, applied patches,
|
||||
timestamp.
|
||||
- Idempotent re-runs; a second run with no upstream change reports "unchanged" and writes nothing.
|
||||
@@ -390,10 +249,7 @@ Repo work that must land before an installer can exist.
|
||||
- Windows: `%ProgramFiles%\RunicGateway\`, data in `%ProgramData%\RunicGateway\`, service
|
||||
registration with automatic start and restart-on-failure.
|
||||
- Both: `UOLINK_CONFIG` and `UOLINK_DB_PATH` pinned in the service definition (§2.3).
|
||||
- Token surfacing (§6): run the installed binary once as
|
||||
`uo-link-sidecar --print-config --config <the pinned path>` **before** registering the service.
|
||||
That both writes the config the service will read and returns the token to print, so the service
|
||||
never starts against a config that does not exist yet.
|
||||
- Token surfacing (§6).
|
||||
|
||||
### Phase 3 — patch tier (opt-in)
|
||||
|
||||
@@ -406,9 +262,9 @@ degrade loudly rather than silently.
|
||||
|
||||
```
|
||||
✓ ServUO found /opt/ServUO (57.4)
|
||||
✓ Overlay in sync 24 files, all hashes match install.json
|
||||
✓ Overlay in sync 23 files, all hashes match install.json
|
||||
⚠ Patch tier 1 of 3 applied — vendor.sale unavailable
|
||||
✓ uo-link installed 1.1.0
|
||||
✓ uo-link installed 3.0.1
|
||||
✓ Service running, enabled
|
||||
✓ Sidecar reachable 127.0.0.1:8080 /health ok
|
||||
✓ Protocol sidecar 3 = overlay manifest 3
|
||||
@@ -418,20 +274,13 @@ degrade loudly rather than silently.
|
||||
The last check matters most: it is the only thing that distinguishes "files copied" from "the bridge
|
||||
actually works" (§2.1).
|
||||
|
||||
Three of those rows are answered by the sidecar's own CLI rather than by inspecting the filesystem:
|
||||
`--version` prints `uo-link-sidecar <ver> (protocol <n>)`, and `--print-config` gives the config and
|
||||
db paths the *installed service* resolves — so `doctor` reports what the binary would actually do,
|
||||
not what `install.json` believes it was told to do. The protocol row compares that number against
|
||||
the overlay manifest's declared one (§7.0).
|
||||
|
||||
`runicgateway update` — resolves the current bundle (§7.1), then acts asymmetrically by component,
|
||||
deliberately:
|
||||
|
||||
- **uo-link**: compare the bundle's version against what is installed → download → verify checksum →
|
||||
replace binary → restart service.
|
||||
- **plugin overlay**: download the bundle's overlay tarball → verify → re-sync (leaving a modified
|
||||
`Bridge.cfg` alone — Phase 1) → record commit → tell the operator ServUO must restart (the
|
||||
installer does not restart the shard).
|
||||
- **plugin overlay**: download the bundle's overlay tarball → verify → re-sync → record commit →
|
||||
tell the operator ServUO must restart (the installer does not restart the shard).
|
||||
|
||||
Because both come from one bundle, an update always moves to a combination whose protocol versions
|
||||
were checked together, rather than to two independently-latest artifacts that may disagree.
|
||||
@@ -476,20 +325,11 @@ Paste these into Admin → Shard on your Runic Gateway site:
|
||||
The token is write-only once saved — the site will never show it back to you.
|
||||
```
|
||||
|
||||
Every value in that block except the host and the site URL comes from one
|
||||
`uo-link-sidecar --print-config` call (§2.4): `web.auth_token`, `protocol`, and `web.bind` +
|
||||
`web.ws_path` for the two URLs. Only the **host** is substituted — `web.bind` is frequently
|
||||
`0.0.0.0`, which is not something to hand a website — so the installer composes the URLs from the
|
||||
host it detects or prompts for, rather than echoing the bind address.
|
||||
|
||||
The installer prompts for the site URL only to build that link; it never contacts the website. A
|
||||
future "installer registers itself with the website" flow (claim code + authenticated endpoint) is
|
||||
explicitly **out of scope** — it is real backend work in a security-sensitive area and can be added
|
||||
later without changing anything here.
|
||||
|
||||
The printed token is a secret in transit: `--print-config` output must go to the operator's
|
||||
terminal and the config file, never into an installer log file or a support bundle.
|
||||
|
||||
---
|
||||
|
||||
## 7. Version tracking, the bundle, and release orchestration
|
||||
@@ -502,41 +342,6 @@ Three components version independently, bound by a protocol contract:
|
||||
- **plugin overlay** — has no queryable version before ServUO boots. The overlay release
|
||||
`manifest.json` declares it, and `install.json` records what was deployed.
|
||||
|
||||
### 7.0 The overlay manifest
|
||||
|
||||
Shipped inside every `runicgateway-overlay-<ver>.tar.gz`, generated by that repo's release workflow:
|
||||
|
||||
```json
|
||||
{
|
||||
"component": "servuo-plugins-overlay",
|
||||
"version": "0.1.0",
|
||||
"commit": "968b526…",
|
||||
"repo": "RunicGateway/servuo-plugins",
|
||||
"protocol": 3,
|
||||
"servuo": { "min_version": "57.4", "patches_verified_against": "57.4" },
|
||||
"files": { "overlay/Config/Bridge.cfg": "32718424…", "patches/…": "…" }
|
||||
}
|
||||
```
|
||||
|
||||
`version` and `commit` come from the release engine; `protocol` and the `servuo` block are read from
|
||||
`servuo-plugins/overlay.toml`; `files` is a SHA256 per shipped file.
|
||||
|
||||
Two of these carry weight beyond documentation:
|
||||
|
||||
- **`protocol` is a hand-maintained declaration, and has to be.** The plugin announces no version on
|
||||
the wire and none is queryable before ServUO boots, so nothing in CI can derive it — which makes
|
||||
this line the only thing §7.1's gate 1 has to compare the sidecar against. The duty is stated in
|
||||
`overlay.toml` and in that repo's README: **bump it in the same PR that changes the emitters**, the
|
||||
way `link` bumps `PROTOCOL_VERSION`.
|
||||
- **`files` is what makes `doctor` able to tell "the operator edited a deployed file" from "the
|
||||
overlay moved on"** (§5, Phase 4). The installer copies these hashes into `install.json` at deploy
|
||||
time; a later mismatch against *both* the manifest and `install.json` means upstream changed, a
|
||||
mismatch against `install.json` alone means local edits.
|
||||
|
||||
`min_version` and `patches_verified_against` are separate on purpose. The base overlay only *adds*
|
||||
files and is expected to work broadly; the patch tier diffs stock ServUO files and is verified
|
||||
against exactly one version (§2.2).
|
||||
|
||||
### 7.1 The bundle manifest
|
||||
|
||||
**The bundle is the compat matrix.** Rather than the installer hardcoding versions or blindly
|
||||
@@ -544,33 +349,15 @@ resolving "latest", CI publishes a small manifest naming an exact, checked combi
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"bundle": "2026.08.04",
|
||||
"generated": "2026-08-04T16:07:13Z",
|
||||
"bundle": "2026.08.01",
|
||||
"protocol": 3,
|
||||
"link": {
|
||||
"repo": "RunicGateway/link", "tag": "v1.1.0", "version": "1.1.0", "protocol": 3,
|
||||
"assets": {
|
||||
"linux-x86_64": { "name": "uo-link-sidecar-linux-x86_64", "url": "…", "sha256": "27d491ef…" },
|
||||
"windows-x86_64": { "name": "uo-link-sidecar-windows-x86_64.exe", "url": "…", "sha256": "fbefd886…" }
|
||||
}
|
||||
},
|
||||
"overlay": {
|
||||
"repo": "RunicGateway/servuo-plugins", "tag": "v0.1.1", "version": "0.1.1",
|
||||
"commit": "3a52abb…", "protocol": 3,
|
||||
"servuo": { "min_version": "57.4", "patches_verified_against": "57.4" },
|
||||
"asset": { "name": "runicgateway-overlay-0.1.1.tar.gz", "url": "…", "sha256": "75dc6d6c…" }
|
||||
}
|
||||
"link": { "version": "3.0.1", "sha256": "a91f..." },
|
||||
"overlay": { "version": "2.4.0", "commit": "a81f42c", "sha256": "7c3e..." }
|
||||
}
|
||||
```
|
||||
|
||||
Note `link.assets` is a **map keyed by platform**, not the single `sha256` this section originally
|
||||
sketched: link publishes a Linux binary and a Windows `.exe`, and the installer runs on both, so one
|
||||
hash could only ever have described one of them. `schema` versions this document's shape and is
|
||||
independent of `protocol` and of either component's release version — all three move separately.
|
||||
|
||||
The installer fetches the current bundle at run time; `--bundle <tag>` pins an older one for a
|
||||
reproducible install. Because the bundle is data, **a new `link` release regenerates ~30 lines of
|
||||
reproducible install. Because the bundle is data, **a new `link` release regenerates ~20 lines of
|
||||
JSON and leaves the installer binary untouched** — operators do not re-download the installer to
|
||||
pick up a sidecar patch, and the installer does not accumulate releases whose code is byte-identical.
|
||||
|
||||
@@ -578,53 +365,19 @@ Two gates run at compose time, both cheap and both worth it:
|
||||
|
||||
1. The sidecar's `PROTOCOL_VERSION` must equal the overlay manifest's declared protocol version.
|
||||
This is the check that catches an `edge`/`main` protocol mismatch before it reaches an operator.
|
||||
The two halves are read from different places because they *are* different: the overlay's from
|
||||
`manifest.json` inside the tarball (the only statement of it that exists — §7.0), the sidecar's
|
||||
from `sidecar/src/main.rs` at the release tag (see Phase 0 item 3 for why not from the binary).
|
||||
2. Every referenced asset must exist and its SHA256 must match the publishing repo's `SHA256SUMS`.
|
||||
The hash recorded in the bundle is the one CI computed from the asset it downloaded, *after* that
|
||||
check — and the installer verifies every download against it. These artifacts are deliberately
|
||||
unsigned (§3), so the checksum is the whole trust anchor; a hash copied from a file nobody
|
||||
verified would make the chain decorative.
|
||||
|
||||
#### Where bundles are published
|
||||
|
||||
Committed to the installer repo under `bundles/`, so the installer's fetch is a plain anonymous
|
||||
`GET` against a public repo — the shard host has no Gitea credentials (§1):
|
||||
|
||||
```
|
||||
bundles/current.json → …/RunicGateway/installer/raw/branch/main/bundles/current.json
|
||||
bundles/bundle-<tag>.json → …/raw/branch/main/bundles/bundle-2026.08.04.json (--bundle)
|
||||
```
|
||||
|
||||
Every bundle is kept forever, so `--bundle` stays reproducible. Tags are UTC dates; a second bundle
|
||||
on the same day — a sidecar release in the morning and an overlay release in the afternoon is the
|
||||
normal way that happens — becomes `2026.08.04.2`, so one tag always names exactly one matrix.
|
||||
|
||||
**Not one Gitea release per bundle**, which was the obvious alternative. This repo's own releases
|
||||
are the installer *binaries*, and `/releases/latest` returns whichever release is newest regardless
|
||||
of kind — interleaving bundle releases would make "latest" intermittently resolve to a release
|
||||
carrying no installer binary. Committing also yields a reviewable diff and a git history of the
|
||||
compat matrix, and needs no new branch-protection exception: `release.yml`'s version-bump commit
|
||||
already requires the CI user to be able to push to `main`.
|
||||
|
||||
### 7.2 What triggers a bundle
|
||||
|
||||
| Trigger | Why |
|
||||
|---|---|
|
||||
| `link` publishes a release | Its release job `POST`s to the installer repo's workflow-dispatch endpoint as its final step |
|
||||
| `servuo-plugins` publishes a release | Same. Phase 0 item 1 gave it the release workflow; the dispatch step was left as a marked TODO until there was something to dispatch, and landed with the bundle CI it calls (item 3) — a step that `404`s on every release is worse than no step |
|
||||
| `link` publishes a release | Its release job `POST`s to the installer repo's workflow-dispatch endpoint as its final step. `link/.gitea/workflows/release.yml` already declares `workflow_dispatch: {}` and already holds a `write:repository` token |
|
||||
| `servuo-plugins` publishes a release | Same, once Phase 0 gives it a release workflow |
|
||||
| Nightly cron on the installer repo | Recomputes from whatever the latest releases actually are, so a missed or failed dispatch self-heals instead of silently pinning operators to a stale sidecar |
|
||||
|
||||
`repository_dispatch` is deliberately avoided — support for it is uncertain on this Gitea version,
|
||||
whereas dispatching an existing `workflow_dispatch` workflow via the API works today.
|
||||
|
||||
**A failed dispatch is a warning, never a failed release.** By the time that step runs the component
|
||||
release is published and correct; failing the job would misreport it. This also keeps the dispatch
|
||||
from becoming a new hard credential requirement — `REGISTRY_TOKEN` having write on the installer
|
||||
repo is a nicety, and without it the nightly cron picks the release up anyway. A dropped dispatch
|
||||
costs latency, not correctness, which is the whole reason the cron exists.
|
||||
|
||||
### 7.3 Stale-overlay handling: dispatch, don't wait
|
||||
|
||||
Each component **self-releases on merge to its own `main`**, using the same conventional-commit
|
||||
@@ -632,12 +385,6 @@ engine. Note that "updated since the last release" must mean *releasable* commit
|
||||
`RELEASE=false` when nothing but `docs:`/`chore:` has landed, so a docs typo correctly does **not**
|
||||
cut an overlay release, and the bundle keeps using the existing one.
|
||||
|
||||
The compose job's copy of that rule additionally **excludes merge commits**, whose subject is
|
||||
`Merge pull request '<the real subject>'`. Without that, every squash-free merge of a `feat:` branch
|
||||
would be counted twice, and worse, a merge of a `docs:` branch whose *title* happens to quote a
|
||||
`fix:` would be read as releasable — re-dispatching, every night, a release workflow that correctly
|
||||
declines to run.
|
||||
|
||||
So by the time the installer's CI looks, the release normally already exists. If it finds
|
||||
`servuo-plugins` main ahead of its latest release *with* releasable commits, it:
|
||||
|
||||
@@ -654,12 +401,11 @@ rather than being silently retriggered every night forever.
|
||||
|
||||
### 7.4 Open risk
|
||||
|
||||
**Settled as of the v3 cutover.** Protocol work landed on `edge` branches and the `edge → main`
|
||||
cutover has now merged, so `main` speaks protocol 3 consistently across the repos. The rule it
|
||||
motivated stands regardless and is not a temporary measure: **the installer hardcodes no protocol
|
||||
version anywhere.** It reads what the artifacts declare, and §7.1's gate 1 is what stops a
|
||||
mismatched pair from being published as a bundle — which is the mechanism that will matter at the
|
||||
*next* protocol bump, not just this one. See `docs/link/v3.md`.
|
||||
The v3 cutover is mid-flight — protocol work landed on `edge` branches with the `edge → main`
|
||||
cutover still open across four repos. Until that lands, `main` and `edge` disagree about
|
||||
`PROTOCOL_VERSION`, so the installer must not hardcode a version anywhere; it reads what the
|
||||
artifacts declare, and §7.1's gate 1 is what stops a mismatched pair from being published as a
|
||||
bundle. See `docs/link/v3.md`.
|
||||
|
||||
---
|
||||
|
||||
@@ -675,13 +421,11 @@ mismatched pair from being published as a bundle — which is the mechanism that
|
||||
3. **Co-location assumption** — the shard dials out to the sidecar on loopback `127.0.0.1:7788`, so
|
||||
sidecar and ServUO must share a host. Should the installer support installing only uo-link on a
|
||||
different host, or hard-assume co-location?
|
||||
Resolved and moved into §1 / §2.2 / §5: uninstall scope, and minimum ServUO version.
|
||||
4. **Branch targeting for the new repo** — `link`, `website`, `servuo-plugins` and `docs` are
|
||||
mid-cutover between `edge` and `main`. The installer repo starts clean on `main`; the Phase 0
|
||||
`servuo-plugins` release workflow needs a target branch decision.
|
||||
|
||||
**Resolved — branch targeting for the new repo** (was question 4). The v3 cutover landed:
|
||||
`servuo-plugins#6` merged, so that repo's `main` and `edge` agree at protocol 3. The release
|
||||
workflow targets `main`, and the installer repo starts clean on `main`. §7.4's caution still applies
|
||||
in principle — the installer hardcodes no protocol version, it reads what the artifacts declare —
|
||||
but the specific `edge`/`main` disagreement that motivated it is gone.
|
||||
Resolved and moved into §1 / §2.2 / §5: uninstall scope, and minimum ServUO version.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# Runic Gateway installer — Project Tree
|
||||
|
||||
> **Auto-generated.** This file is maintained by the `sync-project-tree` CI workflow in
|
||||
> the [`RunicGateway/installer`](https://gitea.whitlocktech.com/RunicGateway/installer) repository, which
|
||||
> opens a pull request here whenever the tracked file layout on `main` changes. Do not edit
|
||||
> by hand — changes will be overwritten by the next sync.
|
||||
|
||||
A snapshot of the tracked files in the repository (build output, dependencies, and other
|
||||
git-ignored paths are excluded).
|
||||
|
||||
```text
|
||||
installer/
|
||||
├── .gitea/
|
||||
│ ├── ISSUE_TEMPLATE/
|
||||
│ │ ├── bug_report.md
|
||||
│ │ ├── config.yaml
|
||||
│ │ └── feature_request.md
|
||||
│ ├── scripts/
|
||||
│ │ └── gen_tree.py
|
||||
│ ├── workflows/
|
||||
│ │ ├── bundle.yml
|
||||
│ │ ├── pr-checks.yml
|
||||
│ │ ├── release.yml
|
||||
│ │ └── sync-project-tree.yml
|
||||
│ └── PULL_REQUEST_TEMPLATE.md
|
||||
├── bundles/
|
||||
│ ├── bundle-2026.08.04.json
|
||||
│ ├── current.json
|
||||
│ └── README.md
|
||||
├── .gitignore
|
||||
├── CODE_OF_CONDUCT.md
|
||||
├── CONTRIBUTING.md
|
||||
├── CONTRIBUTORS.md
|
||||
├── LICENSE.md
|
||||
├── README.md
|
||||
└── SECURITY.md
|
||||
```
|
||||
@@ -23,31 +23,7 @@ Every route **except `GET /health`** requires the shared token from `sidecar.tom
|
||||
| REST | `X-Api-Key: <token>` |
|
||||
| WebSocket | `?token=<token>` in the connect URL (browsers can't set headers on a WS handshake) |
|
||||
|
||||
Missing or wrong token → **401** `{"error":"missing or invalid auth token"}`. The token is compared in constant time. It is generated automatically on first run; rotate by editing `sidecar.toml` and restarting.
|
||||
|
||||
To read it back afterwards, ask the sidecar rather than hunting through the startup log or the TOML:
|
||||
|
||||
```console
|
||||
$ uo-link-sidecar --print-config --config /etc/runicgateway/sidecar.toml
|
||||
{
|
||||
"component": "uo-link-sidecar",
|
||||
"config_created": false,
|
||||
"config_path": "/etc/runicgateway/sidecar.toml",
|
||||
"protocol": 3,
|
||||
"shard": { "bind": "127.0.0.1:7788" },
|
||||
"store": { "path": "/var/lib/runicgateway/uo-link.db" },
|
||||
"token_generated": false,
|
||||
"version": "0.1.0",
|
||||
"web": {
|
||||
"auth_required": true,
|
||||
"auth_token": "c0f04ace…",
|
||||
"bind": "127.0.0.1:8080",
|
||||
"ws_path": "/ws"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That is the same set of values Admin → Shard asks for — base URL and WS URL are `web.bind` (substituting a reachable host if it is `0.0.0.0`) plus `web.ws_path`. The output **contains the token in clear text**, so treat it as a secret: it belongs in a terminal, not in a log or a CI artifact. `--print-config` also performs first-run setup, writing the config file and generating a token if there is none, and reports whether it did via `config_created` / `token_generated`.
|
||||
Missing or wrong token → **401** `{"error":"missing or invalid auth token"}`. The token is compared in constant time. It is generated automatically on first run (the sidecar logs it); rotate by editing `sidecar.toml` and restarting.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ link/
|
||||
│ └── PULL_REQUEST_TEMPLATE.md
|
||||
├── sidecar/
|
||||
│ ├── src/
|
||||
│ │ ├── cli.rs
|
||||
│ │ ├── config.rs
|
||||
│ │ ├── main.rs
|
||||
│ │ ├── rpc.rs
|
||||
|
||||
Reference in New Issue
Block a user