docs(installer): record Phase 3 — the patch tier as built

PLAN.md gains a "Phase 3 as built" section covering the decisions the plan had
left open, and §2.2, §2.2.1 and §7.0 are brought in line with what shipped:

* The engine is fully native. §2.2.1 wrote rung 1 as "apply verbatim with
  git apply", but §1 chose the release tarball so there would be no git on the
  shard host, and rung 2 needs a native applier anyway. Rung 1 keeps its
  stronger verdict and shares rung 2's write path. On the real files this is
  not academic — the shipped patches are CRLF and two of their three targets
  are LF, so git apply refuses patches the installer places correctly.

* §7.0 documents `patch_tier` in the overlay manifest. Which patches form one
  unit, which companion follows which, whether a core rebuild is needed and
  what declining costs are not derivable from a diff, so the release declares
  them and adding a patch regenerates metadata rather than an installer.

* §2.2 gains the pre-image cache and the widened patch cache, and §2.2.1 gains
  the second, per-feature level of the all-or-nothing rule.

INSTALL.md's illustrated tier output is replaced with the real thing, the
status banner now says `install` is complete, §3's path tables list
patches/originals/, and Appendix A2 names the line-ending trap that makes
git apply refuse a patch whose region is visibly untouched.

Refs: RunicGateway/installer#6, RunicGateway/servuo-plugins#10

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-04 19:57:42 -05:00
parent 5ae53d287f
commit 42e6f3a0cb
2 changed files with 182 additions and 39 deletions

View File

@@ -5,12 +5,13 @@ installation and connects it to a Runic Gateway website.
> **Status: the installer binary is not released yet.**
>
> Phases 1 and 2 are built and live on the installer repo's `edge` branch: the installer core
> (bundle resolution, ServUO detection, the overlay sync, `install.json`) and the sidecar half (the
> binary, its config, its service, and the token handoff). What is still missing is the **patch
> tier** ([§4](#4-the-patch-tier-optional)), which `install` reports as not applied rather than
> silently skipping. The first release follows Phase 3, so that a released binary answers every
> question this guide says it answers.
> Phases 1 to 3 are built and live on the installer repo's `edge` branch: the installer core
> (bundle resolution, ServUO detection, the overlay sync, `install.json`), the sidecar half (the
> binary, its config, its service, and the token handoff), and the
> [patch tier](#4-the-patch-tier-optional). `install` is therefore complete; what is still missing
> is [`doctor`, `update` and `uninstall`](#7-day-two), each of which reports which phase it arrives
> in rather than failing as though you had mistyped it. The first release is being cut from that
> branch now.
>
> 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)
@@ -34,7 +35,7 @@ Three things, on the machine that runs your shard:
|---|---|---|
| 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 |
| 3 | **A record of what it did**`install.json`, plus cached copies of the patches and of every file the patch tier edited | Written by the installer |
```
ServUO shard ──loopback TCP 127.0.0.1:7788──► uo-link sidecar ──HTTP + WebSocket──► website
@@ -173,9 +174,9 @@ Overlay sync
ADD Config/Bridge.cfg
ADD Scripts/Custom/Bridge/*.cs (22 files)
CHANGE Scripts/Scripts.csproj
deployed. add=23 change=1 unchanged=0
deployed. add=23 change=1 unchanged=0 kept=0
Patch tier skipped (not selected)
Patch tier not selected
Without it: no vendor.sale events, no in-game moderation audit forwarding.
uo-link sidecar
@@ -230,7 +231,8 @@ reports "unchanged" and writes nothing.
| `/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 |
| `/etc/runicgateway/patches/` | Copies of the patches the tier evaluated, so `uninstall` can print the exact hunks long after the release tarball is gone, and a refused one is still on hand to apply yourself |
| `/etc/runicgateway/patches/originals/` | Each file the patch tier edited, exactly as it was beforehand — a revert you can verify rather than reconstruct |
| `/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 |
@@ -242,6 +244,7 @@ reports "unchanged" and writes nothing.
| `%ProgramData%\RunicGateway\sidecar.toml` | Sidecar config, including the auth token |
| `%ProgramData%\RunicGateway\install.json` | As above |
| `%ProgramData%\RunicGateway\patches\` | As above |
| `%ProgramData%\RunicGateway\patches\originals\` | As above |
| `%ProgramData%\RunicGateway\uo-link.db` | The sidecar's SQLite store |
| Service `RunicGatewayLink` | Automatic start, restart on failure, running as `NT SERVICE\RunicGatewayLink` |
@@ -312,12 +315,27 @@ How the installer handles it:
appear exactly once; anything less and it stops and hands you the hunk to apply by hand. It never
force-fits a patch by loosening the match.
- **All or nothing per feature.** The two vendor-sale patches are one unit and are applied together
or not at all — and within a patch, if one hunk cannot be placed safely, none are.
or not at all — and within a patch, if one hunk cannot be placed safely, none are. A patch that
could have been placed but was held back by its sibling says exactly that; it is never reported as
applied.
- **It does not need `git`, and does not use it.** The matching and the writing are the installer's
own, which is why it can place a patch on a shard where `git apply` refuses — the shipped patches
and their target files do not all use the same line endings, and that alone defeats `git apply`.
Nothing outside a patched region is touched, down to the byte, and inserted lines take your file's
own line ending.
- **Your ServUO version is reported, not decisive** — but see the warning below before running this
on anything other than 57.4.
- **Recorded, and the `.patch` files cached**, so re-runs stay idempotent and `uninstall` can print
the exact hunks to revert — along with how each was applied, since a patch placed into a file you
had already modified is one to look at more carefully when reverting.
had already modified is one to look at more carefully when reverting. Patches that were *not*
applied are cached too, because that is the copy the run tells you to apply by hand.
- **A copy of every file it edits is kept, exactly as it was beforehand**, under
`patches/originals/` in the installer's own directory — not in your ServUO tree. It is written
before the first edit and never overwritten, so however many times you re-run `install`, it stays
the version from before the tier ever touched the file. That is what lets you verify a revert
rather than reconstruct one.
- **Re-running is safe.** A patch already in place is recognised and left alone, and the record
keeps the way it originally landed rather than relabelling it.
### ⚠ On any ServUO that is not 57.4: unsupported, untested, no guarantees
@@ -347,19 +365,29 @@ Because of that, on a non-57.4 tree the tier is off by default and takes a delib
A run where the tier is selected on a shard that has been worked on looks like this:
```
Patch tier 2 of 3 applied
✓ playervendor-sale-eventsink Server/EventSink.cs stock file
✓ playervendor-sale-gump Scripts/Gumps/PlayerVendorGumps.cs
file modified, patched region stock — applied at line 1180
✗ commandlogging-event Scripts/Commands/Logging.cs
patched region has been modified — not applied
apply this hunk by hand, then re-run install:
/etc/runicgateway/patches/commandlogging-event.patch
Patch tier 2 of 3 applied
✓ playervendor-sale-eventsink Server/EventSink.cs
stock file — applied at line 171, 1521, 1771, 2416
✓ playervendor-sale-gump Scripts/Gumps/PlayerVendorGumps.cs
file modified, patched region stock — applied at line 95
✗ commandlogging-event Scripts/Commands/Logging.cs
patched region has been modified (hunk 1) — not applied
apply this by hand, then re-run install to record it:
/etc/runicgateway/patches/commandlogging-event.patch
⚠ Server/EventSink.cs changed — rebuild the core: dotnet build ServUO.sln
Without commandlogging-event: no in-game moderation audit forwarding.
⚠ Server/EventSink.cs — a CORE ServUO file was patched. Rebuild the solution:
dotnet build ServUO.sln
A shard restart is not enough; ServUO's dynamic script build does not rebuild the core, and it
will not tell you so.
Not applied, so you do not get: no in-game moderation audit forwarding.
Everything else works. Apply the hunks by hand if you want them, then re-run install to record it.
```
The line numbers are where each hunk was actually found in *your* file, not where it sits in stock
ServUO — they differ as soon as anything above the region has been edited, and yours is the one to
go to.
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.
@@ -515,7 +543,7 @@ 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 |
| **Printed, not done** | The exact hunks each applied patch added to `EventSink.cs`, `PlayerVendorGumps.cs` and `Logging.cs`, for you to revert — with how each landed, since one placed into a file you had already modified is worth a closer look. The pre-patch copy kept under `patches/originals/` is there to diff against. |
The report is also written to a file, so it survives the scrollback.
@@ -594,7 +622,13 @@ 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`.
`patch` instead, note that some core files are CRLF while others are LF: use `patch --binary`.
**If `git apply` refuses a patch whose target region is visibly untouched, line endings are the
usual cause** — the `.patch` files and their targets do not all use the same ones, and `git apply`
compares them literally. The installer's own tier normalizes line endings and trailing whitespace
for the *comparison* while writing back your file's own endings, which is why it can place patches
`git apply` rejects. Running the installer is the easier route here.
### A3. Install the sidecar