From 29056ba9968cd7efff96e2538379af9903fe4495 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Sat, 1 Aug 2026 05:44:44 -0500 Subject: [PATCH] docs(installer): plan the Runic Gateway installer Design of record for a deployment tool that takes a stock ServUO install and configures it for Runic Gateway. Supersedes the informal overview it grew from, which described a ServUO integration that does not match how servuo-plugins actually ships. Corrections that change the design: - There is no RunicGateway.dll and no Plugins/ dir. The plugin ships as C# source compiled by ServUO at boot, so the step is a hash-compare sync of overlay/ -- but a successful copy does not mean a working bridge, because ScriptCompiler.Compile() ignores dotnet build's exit code and reloads the stale Scripts.dll. - Stock ServUO files ARE modified, by three diffs in patches/. Made an opt-in, skippable tier: git apply against a hand-modified shard will fail, and the EventSink.cs patch needs a full core solution rebuild. - Config paths collided with what the sidecar actually reads. Split ownership: sidecar.toml stays the sidecar's schema, install.json is the installer's. Service definitions pin UOLINK_CONFIG and UOLINK_DB_PATH, since the sidecar writes relative to CWD and would land in VirtualStore under Program Files. - The token handoff was missing entirely -- the largest "installed it and nothing happened" failure mode. - deploy.ps1 cannot be the cross-platform deployer; it stays the developer tool. Decisions: public audience, unsigned binaries anchored on SHA256SUMS, Rust, release-tarball plugin distribution, printed token handoff, new installer repo, warn-and-skip on non-57.4 ServUO, and an uninstall that never edits the shard tree -- it prints the files to delete and the hunks to revert. Phases 0-5, with Phase 0 (a release workflow for servuo-plugins, which has none today) gating everything else. Two open questions remain in section 8. Co-Authored-By: Claude --- installer/PLAN.md | 376 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 installer/PLAN.md diff --git a/installer/PLAN.md b/installer/PLAN.md new file mode 100644 index 0000000..ad89727 --- /dev/null +++ b/installer/PLAN.md @@ -0,0 +1,376 @@ +# Runic Gateway Installer — plan + +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)). + +--- + +## 1. Purpose + +Take a stock ServUO installation and configure it for Runic Gateway with minimal manual steps, while +keeping the components separated and independently maintainable. + +The installer handles environment detection, ServUO overlay deployment, the optional stock-file +patch tier, uo-link installation and service registration, version tracking, diagnostics, and +updates from Gitea releases. + +**It is a deployment tool, not a hosted bootstrapper.** There is no `curl | bash`, no installer +service, and no hosted bootstrap script. Artifacts are downloaded from a Gitea release page and run. + +**It does not replace ServUO startup behavior.** ServUO keeps running through its existing +release/start scripts. The installer never writes a launcher. + +### Decisions locked + +| Question | Decision | +|---|---| +| Audience | **Public** — any ServUO operator, not just shards we run | +| Code signing | **Unsigned.** `SHA256SUMS` is the trust anchor; SmartScreen/Gatekeeper warnings are expected and documented, as with most self-hosted tooling | +| Language | **Rust** — single static binary per OS, reuses the cross-compile pattern already proven in `link/.gitea/workflows/release.yml` | +| Plugin source | **Release tarball artifact** — no git and no Gitea credentials on the shard host | +| Token handoff | **Print token + prefilled admin URL** at the end of the run | +| Repo | **New repo**, `RunicGateway/installer`. It deploys *both* other components, so living inside `link/` would invert the dependency | +| ServUO version | **Warn and skip.** Patches are verified against stock 57.4 only; on anything else the base install proceeds and the patch tier is skipped with a warning. Forks are the norm in a public audience — refusing outright would block most operators | +| Uninstall | **Never touches the ServUO tree.** Removes uo-link and its service entry, then *prints* the overlay files to delete and the patch hunks to revert. Reverting is the operator's call | + +--- + +## 2. Corrections to the original overview + +These are not wording nits — each one changes what the installer has to do. + +### 2.1 There is no `RunicGateway.dll` and no `Plugins/` directory + +The plugin ships as **C# source** and ServUO compiles it at boot. The real deployable is +`servuo-plugins/overlay/`, which mirrors the server root: + +``` +overlay/ +├── Config/Bridge.cfg +└── Scripts/ + ├── Scripts.csproj # Phase 0 — whole-file overwrite of a stock file + └── Custom/Bridge/*.cs # 22 files +``` + +So the plugin step is a hash-compare file sync, not a DLL drop — mechanically easier than the +overview assumed. The sting is that **a successful copy does not mean a working bridge.** Per +`link/SHARD_PREREQS.md`, `ScriptCompiler.Compile()` shells out to `dotnet build`, prints the output, +**ignores the exit code**, and reloads the existing `Scripts.dll`. A broken script build is +invisible: the shard boots clean on stale code. Diagnostics must therefore verify *post-boot* state, +never treat "files copied" as success. + +### 2.2 Stock ServUO files *are* modified — by an optional tier + +`servuo-plugins/patches/` holds unified diffs against stock ServUO 57.4, plus two `.cs` files that +can only be copied *after* their patch lands (they reference symbols the patch introduces): + +| Patch | Target | Companion file | Rebuild required | +|---|---|---|---| +| `playervendor-sale-eventsink.patch` | `Server/EventSink.cs` | `BridgeVendorSale.cs` | **Core** — `dotnet build ServUO.sln`; the dynamic script build is not enough | +| `playervendor-sale-gump.patch` | `Scripts/Gumps/PlayerVendorGumps.cs` | (same unit as above) | script build | +| `commandlogging-event.patch` | `Scripts/Commands/Logging.cs` | `BridgeModerationAudit.cs` | script build | + +Plus `overlay/Scripts/Scripts.csproj`, which overwrites a stock file (Phase 0 — it fixes the silent +ServUO build bug above). + +This is the hardest part of the installer. `git apply` against a hand-modified shard will fail, and +most real shards are hand-modified. Therefore: + +- The patch tier is **opt-in and skippable**. The base install must complete without it. +- Always dry-run (`git apply --check`) before applying, and report per-patch. +- When skipped or failed, say plainly what is lost: **no `vendor.sale` events, no in-game moderation + audit forwarding**. +- The `EventSink.cs` patch must warn loudly that a **core solution rebuild** is required, not just a + shard restart. +- On any ServUO version other than stock **57.4**, skip the whole tier with a warning and continue + with the base install. Do not attempt to apply unverified diffs to an unknown tree. +- Record applied patches in `install.json`, **and cache the applied `.patch` files** next to it + (`/etc/runicgateway/patches/`, `%ProgramData%\RunicGateway\patches\`). Re-runs stay idempotent, + and uninstall can print the exact hunks offline long after the release tarball is gone (§5, + Phase 4). + +### 2.3 Config paths collide with what the sidecar actually reads + +The sidecar reads `$UOLINK_CONFIG`, else `sidecar.toml` in the **working directory** +(`link/sidecar/src/config.rs`), with keys `[shard].bind`, `[web].bind`, `[web].auth_token`, +`[store].path`. The overview proposed a `config.toml` with `[updates]`, `[link]`, `[servuo]` — keys +the sidecar cannot read. + +Two files, two owners: + +| File | Owner | Contents | +|---|---|---| +| `/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 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 +- Windows: binary under `%ProgramFiles%\RunicGateway\`, **data under `%ProgramData%\RunicGateway\`** + +### 2.4 The token handoff was missing entirely + +The whole point is the website reaching the sidecar, and today that is manual and undocumented in +the install flow: the sidecar generates a token on first run and logs it, then a human pastes base +URL, WS URL, token, and protocol version into Admin → Shard, where it is AES-GCM encrypted and +becomes write-only. This is the largest "I installed it and nothing happened" failure mode. + +The installer closes it by printing a copy-paste block at the end of a successful run — see §6. + +### 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 +when the ServUO process is running — correct behavior, and the installer must inherit it (detect and +refuse, rather than corrupt a live `Scripts.dll`). The installer reimplements the sync natively; it +is a short hash-compare-and-copy that never deletes. + +`deploy.ps1` **stays** in `servuo-plugins` as the developer-facing tool. The installer is for +operators. + +### 2.6 Prerequisites the overview assumed away + +- **`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` 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. + +--- + +## 3. Distribution model + +Components are published as Gitea release artifacts. Operators download from the release page +(browser, `curl`/`wget`, or `scp` to the server) and run the binary. + +``` +Runic Gateway Installer v1.0.0 +├── runicgateway-installer-windows-x86_64.exe +├── runicgateway-installer-linux-x86_64 +└── SHA256SUMS + +uo-link v3.x.y (existing release, extended) +├── uo-link-sidecar-windows-x86_64.exe +├── uo-link-sidecar-linux-x86_64 +├── runicgateway-link__amd64.deb (Phase 5) +└── SHA256SUMS + +servuo-plugins v (new release, Phase 0) +├── runicgateway-overlay-.tar.gz # overlay/ + patches/ + manifest.json +└── SHA256SUMS +``` + +```bash +scp runicgateway-installer-linux-x86_64 user@server:/tmp/ +chmod +x runicgateway-installer-linux-x86_64 +sudo ./runicgateway-installer-linux-x86_64 +``` + +### Unsigned-binary posture + +Because releases are unsigned, trust is anchored on checksums and the operator's own verification. +The docs must state this up front rather than let users discover it as a scary dialog: + +- Every release publishes `SHA256SUMS`; the install docs lead with the verification command for both + OSes. +- Windows will show a SmartScreen "unrecognized app" prompt. Documented, with the exact click path. +- The installer verifies the SHA256 of everything **it** downloads (overlay tarball, sidecar binary) + against the release's `SHA256SUMS` and refuses on mismatch. Self-verification is not optional just + because the installer itself is unsigned. +- Revisit signing if it ever becomes affordable; the release layout should not have to change. + +--- + +## 4. Component architecture + +``` + Runic Gateway Installer (Rust, one binary per OS) + │ + ┌───────────────┴────────────────┐ + ▼ ▼ + ServUO integration uo-link + │ │ + ┌────────┴────────┐ ┌────────┴────────┐ + ▼ ▼ ▼ ▼ + overlay sync patch tier (opt-in) binary install service registration + (never deletes) (git apply + guard) + config + data (systemd / Windows SCM) +``` + +Each component keeps its own lifecycle. ServUO's existing startup process is untouched. + +--- + +## 5. Phases + +### Phase 0 — prerequisites (no installer code) + +Repo work that must land before an installer can exist. + +1. **`servuo-plugins`: add `.gitea/workflows/release.yml`.** Retarget the release *engine* half of + `link/release.yml` (its header comment explicitly anticipates this — the plan/release steps + consume only `{version, changelog, artifacts}`). The adapter half produces + `runicgateway-overlay-.tar.gz` containing `overlay/`, `patches/`, and a `manifest.json` + (version, commit, per-file SHA256, declared protocol version, minimum ServUO version). +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. +3. **Decide and document the compat matrix format** (§7). +4. **`docs`: this file, plus `docs/installer/INSTALL.md`** (the operator-facing guide) once the + shape is settled. + +### 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`). +- 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. + +### Phase 2 — uo-link install and service + +- Linux: binary → `/usr/bin/runicgateway-link`, config → `/etc/runicgateway/sidecar.toml`, db → + `/var/lib/runicgateway/`, systemd unit with a dedicated user, `enable` + `start`. +- 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). + +### Phase 3 — patch tier (opt-in) + +Everything in §2.2. Detect applicability, dry-run, apply, record, warn about the core rebuild, and +degrade loudly rather than silently. + +### Phase 4 — diagnostics and updates + +`runicgateway doctor` — the command that makes the whole thing supportable: + +``` +✓ ServUO found /opt/ServUO (57.4) +✓ Overlay in sync 23 files, all hashes match install.json +⚠ Patch tier 1 of 3 applied — vendor.sale unavailable +✓ uo-link installed 3.0.1 +✓ 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 +``` + +The last check matters most: it is the only thing that distinguishes "files copied" from "the bridge +actually works" (§2.1). + +`runicgateway update` — asymmetric by component, deliberately: + +- **uo-link**: query the Gitea releases API → compare versions → download → verify checksum → + replace binary → restart service. +- **plugin overlay**: download the newer overlay tarball → verify → re-sync → record commit → tell + the operator ServUO must restart (the installer does not restart the shard). + +`runicgateway uninstall` — **removes only what it exclusively owns, and never edits the ServUO +tree.** The installer cannot know what the operator has changed in those files since deployment, so +a clever automatic revert risks silently eating their work. It removes and it reports: + +| Action | Scope | +|---|---| +| Removed | uo-link binary, its service entry (systemd unit / Windows service), `install.json` and the cached patch set | +| Kept | `sidecar.toml` and `uo-link.db` (config and history survive; `--purge` to drop them) | +| **Printed, not done** | Every overlay file deployed into the ServUO tree, listed by path, for the operator to delete | +| **Printed, not done** | The exact hunks each applied patch added to `EventSink.cs`, `PlayerVendorGumps.cs`, `Logging.cs`, rendered from the cached `.patch` files, for the operator to revert by hand | + +The printed report is also written to a file, so it survives the terminal scrollback of a long +uninstall. + +### Phase 5 — packaging polish + +`.deb` packaging, Windows MSI, arm64 cross build, and optional automated backup before upgrade. +Deliberately last: v1 can register services directly (`sc create` / a written systemd unit) and ship +plain binaries. Nothing in Phases 1–4 should have to change to add these. + +--- + +## 6. Token handoff (the end of a successful run) + +``` +Runic Gateway is installed. + +One manual step remains — connect the website to this sidecar: + + Base URL http://:8080 + WebSocket URL ws://: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:///admin/shard + +The token is write-only once saved — the site will never show it back to you. +``` + +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. + +--- + +## 7. Version tracking and the compat matrix + +Three components version independently, bound by a protocol contract: + +- **sidecar** — `PROTOCOL_VERSION` in `link/sidecar/src/main.rs`, exposed on `/health` and as + `X-UOLink-Version` on every response; a mismatch is rejected `409`. +- **website** — stores an expected protocol version in `uoLinkConfig` (admin-managed). +- **plugin overlay** — has no queryable version before ServUO boots. The overlay release + `manifest.json` declares it, and `install.json` records what was deployed. `doctor` compares the + recorded overlay protocol version against the sidecar's live one. + +**Open risk:** 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. See `docs/link/v3.md`. + +--- + +## 8. Open questions + +1. **Windows service mechanism** — `sc create` against the plain console binary (simplest, works + today), a bundled WinSW/NSSM shim, or a native `--service` mode in the sidecar using the + `windows-service` crate (cleanest, but changes `link`). Recommendation: `sc create` for v1, + revisit if restart semantics prove inadequate. +2. **Does the installer manage ServUO stop/start?** Currently it refuses while ServUO runs and tells + the operator to restart afterward. Offering to stop/start would be friendlier but means owning + another shard's process lifecycle, and the shard's own start scripts vary. +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? +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 and moved into §1 / §2.2 / §5: uninstall scope, and minimum ServUO version. + +--- + +## 9. Administrator experience + +Before: + +``` +find plugins → copy files → edit ServUO → download bridge → start bridge +→ configure startup → find the token → troubleshoot paths +``` + +After: + +``` +download artifact → verify checksum → run installer → select ServUO directory +→ install components → paste 4 values into Admin → Shard → start ServUO normally +```