diff --git a/installer/INSTALL.md b/installer/INSTALL.md index 474624a..0749c72 100644 --- a/installer/INSTALL.md +++ b/installer/INSTALL.md @@ -5,6 +5,11 @@ installation and connects it to a Runic Gateway website. > **Status: the installer binary is not released yet.** > +> Phase 1 — the installer core: bundle resolution, ServUO detection, the overlay sync and +> `install.json` — is built and lives on the installer repo's `edge` branch. It is deliberately not +> released: a binary that deploys the overlay but cannot yet install the sidecar would not do what +> this guide describes. The first release follows Phase 2. +> > 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) > that names the checked combination of the two. This guide is the operator-facing contract those @@ -496,6 +501,7 @@ The report is also written to a file, so it survives the scrollback. | Symptom | Cause and fix | |---|---| +| **Windows asks for Administrator as soon as you launch it** | Expected, and it needs Administrator anyway. Windows applies *installer detection* to unsigned executables whose file name contains `install` and elevates them before the program starts. Run it from an already-elevated PowerShell and you will not see the prompt. | | **"ServUO is running — stop it before installing"** | Correct, and not overridable. `ServUO.exe` locks `Scripts.dll` and rewrites `Saves/` on exit; deploying underneath it corrupts one or both. Stop the shard, install, start it again. | | Shard boots clean but nothing reaches the site | The classic silent failure: ServUO ignores the script build's exit code and reloaded a **stale `Scripts.dll`**. Run `dotnet build Scripts/Scripts.csproj -c Release -p:Platform=x64` and read the errors it prints. | | `[bridge status` says `connected=False` | The sidecar is not listening on `127.0.0.1:7788`. Check the service is running, and that `[shard] bind` in `sidecar.toml` matches `Host`/`Port` in `Bridge.cfg`. | diff --git a/installer/PLAN.md b/installer/PLAN.md index 62a46e1..b5ea94f 100644 --- a/installer/PLAN.md +++ b/installer/PLAN.md @@ -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 →