wtclaude 52d330167b
Some checks failed
PR Checks / rust-gates (pull_request) Failing after 46s
feat(installer): implement Phase 3 — the patch tier
Two features need edits to stock ServUO sources, because the events they
depend on do not exist. This adds the rung ladder of PLAN.md §2.2.1, the
unsupported-version path of §2.2.2, and the record and cache Phase 4 will read.

Three decisions were not settled by the plan:

* The engine is fully native, with no `git`. §2.2.1 wrote rung 1 as "apply
  verbatim with git apply", but §1 chose the release tarball specifically so
  there would be no git on the shard host, and rung 2 needs a native applier
  regardless. Rung 1 keeps its distinct, stronger verdict — the whole file
  reproduced the diff's `index` pre-image, computed as a git blob SHA1 in
  process — while the write goes through the same code path as rung 2. On the
  real trees here that is not academic: the shipped .patch files are CRLF in a
  Windows checkout and two of their three targets are LF, so `git apply`
  refuses patches this applies correctly.

* Per-patch metadata is declared by the release, with a built-in fallback.
  Which patches form one all-or-nothing unit, which companion .cs follows
  which, whether a CORE rebuild is needed and what declining costs are not
  derivable from a diff. servuo-plugins now declares them; overlay v0.1.1 is in
  the current bundle and declares nothing, so a built-in copy stands in for it.
  A checked-in fixture of the release workflow's own jq output asserts the two
  descriptions are identical, so the repos cannot drift quietly.

* Pre-images are cached in the state directory. The tier edits files the
  operator owns, and `/etc/runicgateway/patches/originals/` is what turns "here
  are the hunks we added" into a revert anyone can verify — kept out of the
  ServUO tree, which uninstall has promised never to clean up.

Everything else follows §2.2.1: exact matching with only line-ending and
trailing-whitespace normalization, exactly one occurrence or it fails,
all-or-nothing per patch file and again per feature, and a byte-preserving
splice so nothing outside a hunk can be reformatted.

Verified against the ServUO 57.4 tree on this machine across four scratch
roots: a hand-patched tree (rung 0), a reverse-applied stock one (rung 1 on the
real EventSink.cs, its blob matching the patch's declared pre-image), a
mixed-rung feature, a tree with edits inside two patched regions (rung 3 —
nothing written, nothing held back applied, no companions copied), and a
non-57.4 tree both with and without the extra consent flag. Three consecutive
runs left install.json byte-identical and the cached pre-image still pre-patch.

Three reporting defects the live runs caught are fixed with tests: a dry run
and a held-back patch both claimed to be "applied", the core-rebuild warning
fired when nothing had been written and named a Scripts file as core, and a
declined tier announced the loss of features install.json showed as applied.
Refused patches are now cached too, since the refusal message names that path.

Refs: docs/installer/PLAN.md §2.2, §5 Phase 3

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 19:54:36 -05:00

Runic Gateway installer

A single-binary deployment tool that takes a stock ServUO installation and configures it for Runic Gateway: deploys the shard plugin overlay, optionally applies the stock-file patch tier, installs the uo-link sidecar and registers it as a service, records what it deployed, and hands the operator the four values that connect the website to the shard.

        ┌──────────────────────────────────────────┐
        │   Runic Gateway installer  (>>> HERE <<<)│
        └───────────────┬──────────────────────────┘
                        │ deploys
        ┌───────────────┴────────────────┐
        ▼                                ▼
  ServUO integration                  uo-link sidecar
  overlay sync + opt-in patch tier    binary + config + service
  (RunicGateway/servuo-plugins)       (RunicGateway/link)

It is a deployment tool, not a hosted bootstrapper — no curl | bash, no installer service. Artifacts are downloaded from a Gitea release page and run.

It also does not replace ServUO startup behavior. ServUO keeps running through its existing release/start scripts; the installer never writes a launcher and never restarts the shard.

Status

Phase 1 (installer core) is built, on the edge branch. Nothing is released yet.

The design of record is installer/PLAN.md in the docs repo: phases, locked decisions, and the Phase 0 prerequisites in other repos (a servuo-plugins release workflow, a non-interactive config read-back in link, and the bundle-manifest CI here), all of which have landed — bundles/current.json names the current protocol-checked sidecar + overlay combination, recomposed on every component release and nightly (see bundles/README.md).

Phase State
0 — prerequisites in the other repos merged
1 — installer core: bundle resolution, ServUO detection, overlay sync, install.json on edge
2 — uo-link install + service registration next
3 — the opt-in stock-file patch tier
4 — doctor, update, uninstall

Why edge: release.yml publishes an installer binary on every push to main, and a binary that deploys the overlay but cannot yet install the sidecar is not something to hand an operator. Phases 1 and 2 land on edge; the edge → main cutover cuts the first release. PRs into edge run the same gates as PRs into main.

Until then, the way to install is by hand — INSTALL.md Appendix A is the same deployment done with curl, tar and systemctl.

Repo What
thisRunicGateway/installer The installer (Rust, one binary per OS).
RunicGateway/link The uo-link sidecar — the network-facing half of the game bridge. Installed and service-registered by this tool.
RunicGateway/servuo-plugins The C# ServUO plugin — deployed as source (overlay/) and compiled by ServUO at boot. Synced into the server tree by this tool.
RunicGateway/website The public site and admin panel. The installer never contacts it — it prints values for Admin → Shard.
RunicGateway/docs All project documentation, including the installer plan.

Planned commands

Command What it does
install Detect and validate the ServUO root, sync the overlay, optionally apply patches, install uo-link + service, write install.json, print the token handoff.
doctor Diagnose an installed deployment end to end — through to "has a shard actually dialed in?", the only check that distinguishes a working bridge from copied files.
update Resolve the current bundle manifest, then update the sidecar (replace + restart) and the overlay (re-sync + tell the operator to restart ServUO).
uninstall Remove only what the installer exclusively owns. It never edits the ServUO tree — it prints the overlay files to delete and the patch hunks to revert, and leaves that call to the operator.

Design constraints worth knowing up front

  • Releases are unsigned. SHA256SUMS is the trust anchor; SmartScreen and Gatekeeper warnings are expected and documented. The installer nonetheless verifies the SHA256 of everything it downloads and refuses on mismatch.
  • Composition comes from a published bundle manifest, not from "latest of each". CI names an exact, protocol-checked combination of sidecar and overlay versions; --bundle <tag> pins one for a reproducible install. A new component release regenerates JSON, not this binary.
  • The base install must complete without the patch tier. The patch tier edits stock ServUO files, most real shards are hand-modified, and unverified ServUO versions skip it with a warning rather than being patched blind.
  • A successful copy is not a working bridge. ServUO ignores the script build's exit code and silently reloads the previous Scripts.dll, so diagnostics verify post-boot state rather than trusting a clean boot.
  • The audience is public — any ServUO operator, not only shards we run.

Build & run

A standard cargo project, with the crate at the repo root:

cargo build --release
cargo run -- --help
cargo run -- install --servuo /path/to/ServUO --verify   # dry run: writes nothing
cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test

RUNICGATEWAY_STATE_DIR relocates install.json (normally /etc/runicgateway or %ProgramData%\RunicGateway), which is how a run is tested without root.

Two layout notes that look odd until you know why:

  • The library target is rgdeploy, not runicgateway_installer. Windows' UAC installer detection refuses to launch an unsigned executable whose file name contains install (os error 740), and Cargo names test harnesses after their target — so a target under that name makes cargo test unrunnable on Windows. The published binary keeps its documented name; [[bin]] test = false keeps Cargo from building a harness under it. Expect a UAC prompt when running the built binary on Windows; it needs Administrator anyway.
  • Cargo.lock is committed, and CI builds --locked.

See CONTRIBUTING.md for the development setup, the local checks CI will run, and the branch/PR workflow.


License

Runic Gateway is free software, licensed under the GNU General Public License v3.0 or later — see LICENSE.md.

Copyright (C) 2026 Runic Gateway

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version. It is distributed WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

Contributions are welcome — please read CONTRIBUTING.md (note the AI-usage disclosure requirement) and our Code of Conduct. Report vulnerabilities privately per SECURITY.md.

Description
No description provided
Readme 451 KiB
Languages
Python 100%