docs(installer): record Phase 1 as built
PLAN.md moves from "Phase 0 complete, no code exists" to "Phase 1 built, on edge", and the Phase 1 section gains an "As built" block in the same shape as the Phase 0 entries — covering the decisions that were not already settled by the design: why the crate lands on `edge` instead of `main`, why the library target is not named after the binary (Windows UAC installer detection makes `cargo test` unrunnable under that name), the dependency choices that follow from the MinGW cross-build, path-based rather than name-based shard-running detection, reading ServUO's version from Server/AssemblyInfo.cs, and the two rules the smoke test corrected — install.json recording a state rather than the run's verb, and the Bridge.cfg keep comparing against the last hash deployed rather than the last hash seen. INSTALL.md gains the same status note and one troubleshooting row: Windows elevates the binary on launch because its file name contains "install", which is expected and needs no action beyond running from an elevated shell. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
# Runic Gateway Installer — plan
|
||||
|
||||
Status: **Phase 0 complete.** Every prerequisite in another repo has landed, the installer repo
|
||||
publishes the bundle manifest, and [`INSTALL.md`](INSTALL.md) now specifies the operator-facing run
|
||||
— so *what* the installer installs and *what using it looks like* both exist ahead of the binary.
|
||||
No installer code exists yet; **Phase 1 is next.** 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
|
||||
Status: **Phase 1 built, on `edge`.** Phase 0's prerequisites all landed, the installer repo
|
||||
publishes the bundle manifest, and [`INSTALL.md`](INSTALL.md) specified the operator-facing run
|
||||
before the binary existed. The crate now exists and implements the installer core — bundle
|
||||
resolution, ServUO detection and validation, the overlay sync and `install.json` — on the
|
||||
`edge` branch, not `main`, so no half-capable binary is released (see
|
||||
[Phase 1 as built](#phase-1--installer-core)). **Phase 2 (uo-link install + service) is next.**
|
||||
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)).
|
||||
|
||||
| Phase 0 item | State |
|
||||
@@ -458,6 +460,79 @@ Repo work that must land before an installer can exist.
|
||||
timestamp.
|
||||
- Idempotent re-runs; a second run with no upstream change reports "unchanged" and writes nothing.
|
||||
|
||||
**As built** ([installer#4](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/4)) — the
|
||||
crate at the repo root, `install` implemented end to end, `doctor`/`update`/`uninstall` parsed and
|
||||
answered with the phase they arrive in rather than "unrecognized command". The decisions that were
|
||||
not already settled above:
|
||||
|
||||
- **It lands on `edge`, not `main`.** `release.yml` publishes an installer binary on every push to
|
||||
`main`, and its crate guard was written to arm "the moment Phase 1 lands the crate" — which would
|
||||
have published a binary that deploys the overlay but cannot install the sidecar, contradicting
|
||||
everything `INSTALL.md` promises a release does. Phases 1 and 2 land on `edge`; the `edge → main`
|
||||
cutover cuts the first release. `pr-checks.yml` gates PRs into `edge` on the same rules, so the
|
||||
branch where the work happens is not the ungated one. No workflow needed a temporary edit.
|
||||
- **The run says what it did *not* do.** A Phase 1 `install` ends with an unmissable block naming
|
||||
the sidecar as not installed, pointing at `INSTALL.md` A3/A4, and printing the bundle's binary URL
|
||||
and SHA256 so a hand install matches the pair. `--patches` is the sharp edge here: it is accepted
|
||||
(so the flag surface is the published one) but reports `REQUESTED BUT NOT APPLIED — no stock
|
||||
ServUO file has been touched`. A `--patches` run that completed quietly would be read as a
|
||||
patched shard.
|
||||
- **The crate is a library plus a thin binary, and the library is not named after it.** Windows
|
||||
applies UAC *installer detection* to unsigned executables whose file name contains `install`: it
|
||||
demands elevation before the process starts, and a non-interactive session gets `os error 740`
|
||||
instead of a program. That is survivable for the shipped binary — it needs Administrator anyway,
|
||||
and `INSTALL.md` already says to run it from an elevated shell — but Cargo names test harnesses
|
||||
after their target, so a target called `runicgateway_installer` makes `cargo test` **unrunnable on
|
||||
Windows**, on the machine the shard smoke tests live on. The code therefore sits in a library
|
||||
called `rgdeploy`, the binary target keeps its published name, and `[[bin]] test = false` stops
|
||||
Cargo building a harness under it. Nothing an operator sees changes.
|
||||
- **Dependencies chosen for the MinGW cross-build:** `ureq` (blocking HTTP over rustls/ring — no
|
||||
OpenSSL to cross-compile, and no async runtime for a tool that makes four sequential requests),
|
||||
`flate2` on its pure-Rust backend, `tar`, `sha2`, `serde`/`serde_json`, `chrono`, `anyhow`, and
|
||||
`sysinfo` for the running-shard check.
|
||||
- **The shard-running check matches by path, not by process name.** `deploy.ps1` can look for a
|
||||
process called `ServUO` because it only runs on Windows; on Linux the same shard is `mono` or
|
||||
`dotnet` with `ServUO.exe` as an argument, and a name match would answer "not running" for a live
|
||||
shard — the one wrong answer that corrupts `Scripts.dll`. The installer requires a process whose
|
||||
executable or command line names *both* the tree being deployed into and `ServUO.exe`, so a second
|
||||
shard elsewhere on the host does not block this deploy, and the installer never matches itself.
|
||||
- **ServUO's version is read from `Server/AssemblyInfo.cs`**, not from `ServUO.exe`'s PE metadata:
|
||||
it is the same *source* tree the patch tier diffs against, needs no dependency, and works
|
||||
identically on Linux. `57.4.0.0` and `57.4` are normalized to compare equal. An unreadable version
|
||||
is reported as `unknown` and treated as **not** supported — an unreadable version is not evidence
|
||||
of a good one — which is what Phase 3 will gate the tier on.
|
||||
- **`install.json` records a state, not a verb.** Per-file entries are `deployed` or
|
||||
`kept-operator-modified`, never `add`/`change`/`unchanged`. Recording the run's verb made the
|
||||
record differ between a first run and an identical second one, which rewrote the file on every
|
||||
run and broke "a second run writes nothing" in the least visible way available. What later
|
||||
commands need is whose copy is in the tree, and that does not change because time passed.
|
||||
- **The `Bridge.cfg` decision compares against the last hash the installer *deployed*, not the last
|
||||
hash it *saw*.** Once a file has been kept, the record's on-disk hash is the operator's content —
|
||||
so a rule phrased as "is the tree still what the record last saw?" matches on the very next run
|
||||
and overwrites exactly the file it had just protected. A keep has to stay kept for as long as the
|
||||
edit is there; a live three-run test covers it, because the bug only appears from the second run
|
||||
on.
|
||||
- **A prior record is only consulted when it names this tree.** A host whose `install.json` points
|
||||
at a different ServUO root — a shard moved or rebuilt beside the old one — is treated as having no
|
||||
prior deployment, which errs toward keeping the operator's file.
|
||||
- **The download is verified twice, for two different reasons.** The tarball's SHA256 is checked
|
||||
against the bundle while it is being written (the trust anchor — these artifacts are unsigned);
|
||||
then every extracted file is re-hashed against the release's own `manifest.json`, which catches a
|
||||
truncated extraction and is what makes the hashes copied into `install.json` worth trusting. The
|
||||
manifest's `protocol` and `version` are also cross-checked against the bundle, so an artifact that
|
||||
disagrees with the matrix that named it stops the run before anything is written.
|
||||
- **`RUNICGATEWAY_STATE_DIR` relocates the installer's own state**, so a run can be tested without
|
||||
root. Documented in `--help` rather than hidden: an undocumented variable that moves where a tool
|
||||
writes is worse than a documented one, and `doctor` must honour the same value to find what
|
||||
`install` wrote.
|
||||
|
||||
Verified on this machine against a real ServUO 57.4 tree (`--verify`, which reported the tree's
|
||||
`Bridge.cfg` as operator-owned and 23 code files as changed) and end to end into a scratch tree:
|
||||
24 files deployed, a second run reporting `unchanged` and leaving `install.json` untouched, an
|
||||
edited `Bridge.cfg` kept across three further runs while a hand-edited `.cs` was overwritten each
|
||||
time, a pinned `--bundle`, a missing bundle tag, and a refusal — pid and path named, exit 1 — with a
|
||||
process running out of the tree.
|
||||
|
||||
### Phase 2 — uo-link install and service
|
||||
|
||||
- Linux: binary → `/usr/bin/runicgateway-link`, config → `/etc/runicgateway/sidecar.toml`, db →
|
||||
|
||||
Reference in New Issue
Block a user