Files
docs/installer/PLAN.md
wtclaude 29056ba996 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 <noreply@anthropic.com>
2026-08-01 05:44:44 -05:00

18 KiB
Raw Blame History

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).


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 Coredotnet 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_<ver>_amd64.deb        (Phase 5)
└── SHA256SUMS

servuo-plugins v<ver>                 (new release, Phase 0)
├── runicgateway-overlay-<ver>.tar.gz         # overlay/ + patches/ + manifest.json
└── SHA256SUMS
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-<ver>.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.
  • 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 uninstallremoves 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 14 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://<this-host>:8080
  WebSocket URL     ws://<this-host>: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>/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:

  • sidecarPROTOCOL_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 mechanismsc 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 repolink, 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