Files
installer/README.md
wtclaude dff4ad41c9
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m31s
feat(installer): implement Phase 1 — the installer core
Adds the Rust crate at the repo root and implements `install` end to end for
the overlay half of a deployment: resolve the published bundle, find and
validate the ServUO root, refuse to deploy under a running shard, sync the
plugin overlay, and record what was deployed in install.json.

`doctor`, `update` and `uninstall` parse and answer with the phase they arrive
in rather than "unrecognized command", and the run states plainly that the
uo-link sidecar (Phase 2) and the patch tier (Phase 3) were not installed —
`--patches` in particular reports REQUESTED BUT NOT APPLIED, since a quiet
completion would be read as a patched shard.

Landing on `edge` rather than `main`: release.yml publishes a binary on every
push to main, and an installer that deploys the overlay but cannot install the
sidecar is not something to hand an operator. pr-checks.yml now gates PRs into
edge on the same rules, so the branch the work happens on is not the ungated
one.

Notable decisions, all documented in docs/installer/PLAN.md §5 Phase 1:

- The code lives in a library called `rgdeploy` with a thin binary that keeps
  the published name. 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 running-shard check matches processes by path, not by process name:
  on Linux a live shard is `mono`/`dotnet` with ServUO.exe as an argument, and
  a name match would report "not running" for a shard that is running.
- install.json records a state (`deployed` / `kept-operator-modified`), not the
  run's verb, so an unchanged re-run produces an identical record and writes
  nothing.
- The Bridge.cfg keep rule compares against the hash the installer last
  deployed, not the last hash it saw — otherwise a kept file is overwritten on
  the very next run.
- Downloads are verified against the bundle's SHA256 while being written, then
  every extracted file is re-hashed against the release's own manifest.json,
  whose protocol and version are cross-checked against the bundle.

Verified against a real ServUO 57.4 tree and end to end into a scratch tree:
24 files deployed, an unchanged re-run that writes nothing, an edited
Bridge.cfg kept across repeated runs while code files are overwritten, bundle
pinning, and a refusal with a shard running out of the tree.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 14:58:17 -05:00

143 lines
7.7 KiB
Markdown

# 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`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/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`](bundles/current.json) names the current protocol-checked
sidecar + overlay combination, recomposed on every component release and nightly
(see [`bundles/README.md`](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](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/installer/INSTALL.md#appendix-a--installing-by-hand)
is the same deployment done with `curl`, `tar` and `systemctl`.
## Related repos
| Repo | What |
|------|------|
| **this**`RunicGateway/installer` | The installer (Rust, one binary per OS). |
| [RunicGateway/link](https://gitea.whitlocktech.com/RunicGateway/link) | The **uo-link sidecar** — the network-facing half of the game bridge. Installed and service-registered by this tool. |
| [RunicGateway/servuo-plugins](https://gitea.whitlocktech.com/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](https://gitea.whitlocktech.com/RunicGateway/website) | The public site and admin panel. The installer never contacts it — it prints values for Admin → Shard. |
| [RunicGateway/docs](https://gitea.whitlocktech.com/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:
```bash
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](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](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](CONTRIBUTING.md) (note
the **AI-usage disclosure** requirement) and our
[Code of Conduct](CODE_OF_CONDUCT.md). Report vulnerabilities privately per
[SECURITY.md](SECURITY.md).