diff --git a/installer/INSTALL.md b/installer/INSTALL.md index 1ef089b..a9b379c 100644 --- a/installer/INSTALL.md +++ b/installer/INSTALL.md @@ -213,12 +213,17 @@ reports "unchanged" and writes nothing. | `--verify` | `install`, `update` | Dry run. Report every change that would be made; write nothing. | | `--servuo ` | `install`, `doctor`, `update` | Name the ServUO root instead of detecting or prompting. | | `--bundle ` | `install`, `update` | Pin an exact published bundle instead of the current one. | -| `--patches` / `--no-patches` | `install` | Decide the patch tier non-interactively. `--patches` never loosens the region check: patches whose target lines are not stock are reported for you to apply by hand, not forced. | +| `--patches` / `--no-patches` | `install`, `update` | Decide the patch tier non-interactively. `--patches` never loosens the region check: patches whose target lines are not stock are reported for you to apply by hand, not forced. On `update` it is what takes up a feature the shard does not already have. | | `--patches-unsupported-servuo` | `install` | Required *in addition to* `--patches` to run the patch tier on a ServUO that is not 57.4. Unsupported and untested — see [§4](#4-the-patch-tier-optional). Ignored on 57.4. | | `--host ` | `install` | The hostname to print in the website URLs. | | `--site-url ` | `install` | Your site's base URL, for the Admin → Shard link. | -| `--yes` | all | Assume the default answer to every prompt. Combine with the flags above for an unattended run. | -| `--purge` | `uninstall` | Also delete `sidecar.toml` and `uo-link.db`, which are otherwise kept. | +| `--yes` | all | Assume the default answer to every prompt. Combine with the flags above for an unattended run. **On `uninstall` it means yes** — that prompt defaults to no, and typing `uninstall --yes` is not an accident. | +| `--purge` | `uninstall` | Also delete `sidecar.toml`, `uo-link.db` and the cached patch set, all of which are otherwise kept. | + +Exit codes are `0` success, `1` the run failed, `2` the arguments were unusable. Two commands also +use `1` for a run that *completed* and found something wrong, so they can be read from a script: +`doctor` when any check failed, and `uninstall` when a step could not be carried out (everything +else still was). --- @@ -504,21 +509,41 @@ The command that makes this supportable. Run it before asking anyone for help first thing a maintainer will want. ``` +✓ Install record /etc/runicgateway/install.json (bundle 2026.08.04, installer 1.0.0, …) ✓ ServUO found /opt/ServUO (57.4) ✓ Overlay in sync 24 files, all hashes match install.json -⚠ Patch tier 1 of 3 applied (region-match) — vendor.sale unavailable -✓ uo-link installed 1.1.0 -✓ Service running, enabled -✓ Sidecar reachable 127.0.0.1:8080 /health ok +⚠ Patch tier 1 applied — moderation-audit (region-match) +✓ uo-link installed uo-link-sidecar 1.1.0 (protocol 3) + config /etc/runicgateway/sidecar.toml database /var/lib/runicgateway/uo-link.db +✓ Service runicgateway-link.service active, enabled as runicgateway +✓ Sidecar reachable 127.0.0.1:8080 /health ok, up 6h, database ok ✓ Protocol sidecar 3 = overlay manifest 3 -✗ Shard connected no shard has dialed in since boot +✗ Shard connected no — the shard is running (pid 8123) but has not dialed in +✓ Bundle 2026.08.04 — up to date ``` -Three of those rows come from asking the installed sidecar (`--version`, `--print-config`) rather -than from reading `install.json`, so `doctor` reports what the binary would actually do — including -which config and database file the *service* resolves — rather than what the installer believes it -was told. The overlay row compares live file hashes against both `install.json` and the release -manifest, which is how it tells "you edited a deployed file" from "the overlay moved on". +Rows come from asking the installed sidecar (`--version`, `--print-config`) rather than from reading +`install.json`, so `doctor` reports what the binary would actually do — including which config and +database file the *service* resolves — rather than what the installer believes it was told. The +overlay row compares live file hashes against `install.json`, which is how it tells "you edited a +deployed file" from "the file is gone"; the bundle row is what tells you the overlay upstream has +moved on. Each patched file is re-checked against the cached copy of its patch, so a core upgrade or +a restored backup that quietly removed the tier's edits is caught here — nothing else would notice. + +It writes nothing at all, and it is safe to run while the shard is up; that is in fact the only +state in which the last row can be `✓`. + +**Reading the marks:** + +| | | +|---|---| +| `✓` | as it should be | +| `⚠` | worth knowing, not broken — a stopped shard, a service you never registered, an unpatched tier, or no route to Gitea to check for a newer bundle | +| `✗` | broken. `doctor` exits `1` if any row is `✗`, so it can be run from a monitoring script; a `⚠` never causes that | + +The distinction on the last row is worth spelling out: **shard not running** is a `⚠` (start it), +while **shard running and not dialed in** is a `✗` — that is the silent failure this whole guide +warns about, where ServUO reports a clean boot over a script build that failed. ### `runicgateway update` @@ -532,6 +557,21 @@ together — never to two independently-latest artifacts that may disagree. Your `sidecar.toml`, your `Bridge.cfg` edits and your database are not touched. `Bridge.cfg` is overwritten only if you have not changed it; a modified copy is reported, not clobbered. +It updates the ServUO tree `install.json` names — not a tree it detects — and it needs the shard +stopped, exactly as `install` does. There is nothing to update on a host that was never installed; +it says so rather than performing a first install under a verb that promises to preserve. + +**Your auth token is not reprinted.** It has not changed and your website already has it. The one +thing an update can change that the site must be told about is the **protocol version**, and it says +so plainly when that happens — a stale number in Admin → Shard is answered with `409` and looks +exactly like your shard going offline. + +**The patch tier under `update`:** features you already have are re-checked against the new release +(normally nothing to do), without asking you again — you consented when they were installed, and +that includes a shard where the tier ran unsupported. Features you never took are **named, not +applied**; run `update --patches` (or `install --patches`) to take one up. A shard that declined the +tier stays unpatched through every update. + ### `runicgateway uninstall` Removes what it exclusively owns, and **prints** everything else. The installer cannot know what you @@ -540,12 +580,19 @@ 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 | +| **Removed** | The sidecar binary, its service entry, `install.json` | +| **Kept** | `sidecar.toml`, `uo-link.db`, and the cached patch set with its pre-patch originals (`--purge` drops all of them) | +| **Printed, not done** | Every overlay file deployed into your ServUO tree, by path, for you to delete — with any file you have edited since deployment flagged, so you do not delete your own work by mistake | | **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. +It lists all of that **before** asking, and the prompt defaults to **no**. `--yes` proceeds, which is +what an unattended uninstall needs; nothing else about the command is destructive to your shard, +which is neither stopped nor started. + +The report is also written to a file — `runicgateway-uninstall-.txt` in the directory you +ran the command from — so it survives the scrollback. That is why the cached patches and the +originals stay behind by default: they are the only offline record of what the tier changed once the +release tarball is gone, and the report tells you to diff against them. --- diff --git a/installer/PLAN.md b/installer/PLAN.md index 0f19880..95d9800 100644 --- a/installer/PLAN.md +++ b/installer/PLAN.md @@ -1,15 +1,16 @@ # Runic Gateway Installer — plan -Status: **Phases 1, 2 and 3 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 +Status: **Phases 1 to 4 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 implements the installer core (bundle resolution, ServUO detection and validation, the overlay sync, `install.json` — [Phase 1 as built](#phase-1--installer-core)), the sidecar half (binary, config, service, token handoff — -[Phase 2 as built](#phase-2--uo-link-install-and-service)), and the patch tier (the rung ladder, the -unsupported-version path, the cached patch set — [Phase 3 as built](#phase-3--patch-tier-opt-in)). -All three are on the `edge` branch, not `main`, so no half-capable binary is released. **The -`edge → main` cutover is next**, and it now cuts a binary that does everything `INSTALL.md` -describes except `doctor`/`update`/`uninstall`, each of which says which phase it arrives in. +[Phase 2 as built](#phase-2--uo-link-install-and-service)), the patch tier (the rung ladder, the +unsupported-version path, the cached patch set — [Phase 3 as built](#phase-3--patch-tier-opt-in)), +and the day-two commands `doctor`, `update` and `uninstall` ([Phase 4 as +built](#phase-4--diagnostics-and-updates)). All four are on the `edge` branch, not `main`, so no +half-capable binary is released. **The `edge → main` cutover is next**, and it now cuts a binary +that does everything `INSTALL.md` describes — Phase 5 is packaging polish, not capability. 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)). @@ -106,7 +107,9 @@ most real shards are hand-modified. Therefore: (`/etc/runicgateway/patches/`, `%ProgramData%\RunicGateway\patches\`). Re-runs stay idempotent, and uninstall can print the exact hunks offline long after the release tarball is gone (§5, Phase 4). As built this caches every patch the tier *evaluated*, not only those that applied, - because the refusal message names that path as the file to apply by hand. + because the refusal message names that path as the file to apply by hand. **The cache outlives an + uninstall** — see Phase 4, where the report that would have been left pointing at a deleted + directory is what settled it. - **Cache the pre-image of every file the tier edits**, under `patches/originals/`, mirroring its path in the ServUO tree. It is written before the first edit and never overwritten, so a revert can be verified byte-for-byte rather than reconstructed from a printed diff — which matters most @@ -754,14 +757,87 @@ a clever automatic revert risks silently eating their work. It removes and it re | Action | Scope | |---|---| -| Removed | uo-link binary, its service entry (systemd unit / Windows service), `install.json` and the cached patch set | -| Kept | `sidecar.toml` and `uo-link.db` (config and history survive; `--purge` to drop them) | +| Removed | uo-link binary, its service entry (systemd unit / Windows service), `install.json` | +| Kept | `sidecar.toml`, `uo-link.db`, and the cached patch set with its pre-patch originals (`--purge` to drop them) | | **Printed, not done** | Every overlay file deployed into the ServUO tree, listed by path, for the operator to delete | | **Printed, not done** | The exact hunks each applied patch added to `EventSink.cs`, `PlayerVendorGumps.cs`, `Logging.cs`, rendered from the cached `.patch` files — with the rung that applied each one (§2.2.1), since a `region-match` apply means the surrounding file was already the operator's — for them to revert by hand | The printed report is also written to a file, so it survives the terminal scrollback of a long uninstall. +**As built** ([installer#7](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/7)) — +`src/doctor.rs`, `src/update.rs` and `src/uninstall.rs`, plus `service::observe`/`service::remove` +and a `Mode` on the install pipeline. The decisions that were not already settled above: + +- **`update` is the `install` pipeline in a different mode, not a second implementation.** This + section describes it as "re-resolve the bundle, then move both components to it" — which is what + an `install` over an existing deployment already does, down to keeping a modified `Bridge.cfg` + and restarting the service after replacing its binary. A separate implementation would have given + the sync rules, the two protocol cross-checks and the record-carrying logic a second place to + disagree. What actually differs is four things: a prior record is **required** (an `update` on an + uninstalled host is a typo or a state directory the run cannot see — never a first install under + a verb that promises to preserve), the tree comes from that record rather than from detection (a + host with two shards must not have an update silently move to the other one), the tier's scope + narrows, and the close is a diff instead of a handoff. +- **`update` does not reprint the token, and does call out a protocol change.** The token has not + changed and the website already holds it; reprinting a secret nobody has to act on just puts it + in another scrollback. The protocol number is the one thing an update *can* change that the + website has to be told about — a stale value in Admin → Shard is answered `409` and looks to an + operator exactly like the shard going offline. +- **The tier under `update` re-resolves only what an earlier run applied, without asking again.** + Not a fresh offer: a shard that declined stays unpatched through every update, which is what + opt-in has to mean. Consent is not re-sought for what is already in the tree — including on an + unsupported ServUO, where `install` demands a second flag — because the record *is* the evidence + that the operator opted in, and re-prompting would make an unattended update impossible on + precisely the hosts that most need their patches re-checked when an overlay moves. New features + the release offers are named but not applied; `--patches` is how they are taken up. A feature the + record shows as applied that the release no longer declares keeps its record rather than being + dropped: its edits are still in the tree, and a record that forgot them would stop `uninstall` + printing hunks that are really there. +- **`doctor` asks the thing itself, and asks it the way the service does.** `--print-config` is run + under the same `UOLINK_DB_PATH` the unit pins, so the config and database it names are the ones + the *service* opens rather than the ones the binary would pick on its own — which is what §5's + sketch promised and a bare call would have got wrong on Linux. It is also run **only when the + config already exists**, because that flag provisions: a diagnosis must not create the state it + is reporting on. +- **`doctor` exits `1` when a row failed, and a `⚠` never causes that.** The rule makes it readable + from a monitoring script, and the split is what keeps the report worth reading: a stopped shard + is a `⚠` with the reason ("you have not started it"), while a *running* shard that has not dialed + in is the `✗` (§2.1's silent failure). Being offline is a `⚠` too — a shard host with no route to + Gitea is a supported way to run this, and failing a health check over it would report a working + deployment as broken. Both network calls take short timeouts for the same reason. +- **The patch row re-resolves each recorded patch against the tree.** The cached `.patch` makes it + possible offline, and the expected answer is rung 0. A core upgrade, a hand revert or a restored + backup silently removes the tier's edits, and nothing else in the report would notice. +- **The cached patch set and `patches/originals/` survive an uninstall** — a deviation from the + table above, which listed them as removed. The report that same command prints tells the operator + to diff their stock files against those originals; deleting them would have made the advice + impossible to follow within one command's output. They are the only offline record of what the + tier changed once the release tarball is gone, so `--purge` is what removes them, alongside the + config and the database. The report names every path it left behind. +- **`--yes` means yes on `uninstall`, not "take the default".** Everywhere else that flag answers an + offer the *run* made, so taking the safe default is right. Here the operator typed the destructive + verb; reading `--yes` as "no" would leave an unattended uninstall unable to express itself at all, + and a script that appears to succeed while removing nothing is the worse of the two failures. The + interactive prompt still defaults to **no**, after listing exactly what will and will not be + touched. +- **`uninstall` exits `1` for a step it could not carry out**, having done everything else. The + common case is a binary still locked by a sidecar somebody started by hand, so a permission error + on that file says so rather than sending the operator to look at ACLs. The Linux service account + is removed only when the record says this installer created it; Windows' virtual account goes with + the service. +- **The overlay listing flags files edited since deployment.** An operator deleting that list file + by file must not lose their own `Bridge.cfg` settings or a script edit without being told which + ones those are. + +Verified on this machine against a scratch ServUO 57.4 tree built from the real files: a healthy +`doctor` (exit 0), one against a tree with a deleted overlay file, an edited one and a reverted +patch (all three found, exit 1), an `update --verify` that wrote nothing, a real `update` that +repaired all three and left `install.json` byte-identical, `uninstall` with and without `--purge`, +a second `uninstall`, a locked binary reported as a problem with exit 1, and `doctor`/`update` on a +host with no record. `fmt`/`clippy -D warnings`/tests were run for Linux in Docker as well as on the +Windows host, since only half of `service.rs` compiles on either. + ### Phase 5 — packaging polish `.deb` packaging, Windows MSI, arm64 cross build, and optional automated backup before upgrade.