From 38f83ad2e0c24cd1547da7d59b46afdda5dd78d7 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 5 Aug 2026 05:19:55 -0500 Subject: [PATCH 1/3] docs(installer): settle Phase 5's scope Phase 5 was sketched as four items; two of them are dropped rather than deferred, because what stops them is an ownership conflict that does not improve with time (org lead, 2026-08-05). - No .deb and no MSI (5.1). A .deb under link's release would own the binary, the systemd unit and the service user -- the three things service.rs writes, hardens and removes and install.json records, so uninstall would leave a dpkg-installed-but-broken package and an apt upgrade would make doctor report drift nobody caused. The binary-only variant buys apt-managed upgrades of one file, which update already does from a protocol-checked bundle. An MSI contradicts "the installer does not install itself" and adds a second uninstall path beside the verb that owns install.json, the cached patch set and the ServUO-tree report. - Linux aarch64 for both components (5.2), in the order the bundle CI forces: it hard-fails on an unrecognized link asset name and asserts the platform keys present, so the name is taught on main first, link publishes, the key becomes required, and only then does the crate on edge learn it. bundle.yml is never edited on edge, so the cutover merge has nothing to conflict over. - Backup before overwrite (5.3), scoped by what cannot be fetched again: not the binary or the overlay files, and not the database (store.rs is CREATE TABLE IF NOT EXISTS over shard state the sweeps repopulate -- a cache with a schema), but an operator's edits to a deployed .cs file, which Phase 1 overwrites by design, and sidecar.toml, whose token the website already holds. - The docs a first release invalidates (5.4), including the repo README still announcing Phase 1 four phases later. Also records that the Windows SCM smoke was attempted on 2026-08-05 and stopped at its first check on an unelevated shell, so that half remains entirely unexecuted. Co-Authored-By: Claude (cherry picked from commit 8818c06f1dd2b5403a3089b65d0782eb6f0fa9f0) --- installer/PLAN.md | 161 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 143 insertions(+), 18 deletions(-) diff --git a/installer/PLAN.md b/installer/PLAN.md index 449b352..d2edf7d 100644 --- a/installer/PLAN.md +++ b/installer/PLAN.md @@ -17,7 +17,10 @@ release layout is exactly what Phase 5 changes. So the `edge → main` cutover i things: 1. **Phase 5, packaging polish** (§5) — the last work before a first release, rather than the first - work after it. + work after it. Its scope is now settled: **no `.deb` and no MSI** (§5.1 — both would give the + service, its unit and its user a second owner), **Linux `aarch64` for both components** (§5.2), + **a backup of what an upgrade overwrites** (§5.3), and the docs a first release invalidates + (§5.4). 2. **The Windows SCM half being verified on a real host.** systemd registration has now been run for real (see [Phase 2 as built](#phase-2--uo-link-install-and-service)), and doing it found a bug no amount of unit testing had. `sc create`, the virtual service account, the failure actions and the @@ -277,9 +280,11 @@ operators. - **`servuo-plugins` had no release workflow.** Only `link` did. "Pull latest repository" is replaced by a release tarball, which had to be built first — Phase 0 item 1, now in review ([servuo-plugins#7](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/pulls/7)). -- **arm64 is not buildable today.** `link/release.yml` cross-compiles only - `x86_64-unknown-linux-gnu` and `x86_64-pc-windows-gnu`. An arm64 `.deb` needs another cross - toolchain. +- **arm64 was not buildable today.** `link/release.yml` cross-compiled only + `x86_64-unknown-linux-gnu` and `x86_64-pc-windows-gnu`, so `platform_key()` refused every other + host by name. That was a property of the workflows, not of Rust — Phase 5 adds + `aarch64-unknown-linux-gnu` to both components in the order §5.2 sets out. There is no `.deb`, so + the cross toolchain is the whole cost. - **The compat matrix has no home.** `PROTOCOL_VERSION` lives in `link/sidecar/src/main.rs`. The sidecar publishes it via `X-UOLink-Version` and `/health`, and the website stores an expected value — but the *plugin's* protocol version is not queryable before boot. Phase 0 item 1 gives it @@ -296,12 +301,13 @@ Components are published as Gitea release artifacts. Operators download from the Runic Gateway Installer v1.0.0 ├── runicgateway-installer-windows-x86_64.exe ├── runicgateway-installer-linux-x86_64 +├── runicgateway-installer-linux-aarch64 (Phase 5) └── SHA256SUMS uo-link v1.1.0 (existing release, extended) ├── uo-link-sidecar-windows-x86_64.exe ├── uo-link-sidecar-linux-x86_64 -├── runicgateway-link__amd64.deb (Phase 5) +├── uo-link-sidecar-linux-aarch64 (Phase 5) └── SHA256SUMS servuo-plugins v (new release, Phase 0) @@ -794,7 +800,7 @@ 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` | -| Kept | `sidecar.toml`, `uo-link.db`, and the cached patch set with its pre-patch originals (`--purge` to drop them) | +| Kept | `sidecar.toml`, `uo-link.db`, the cached patch set with its pre-patch originals, and (from Phase 5) the backups an upgrade took — `--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 | @@ -876,21 +882,138 @@ 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. -Deliberately last *of the build phases*: v1 can register services directly (`sc create` / a written -systemd unit) and ship plain binaries. Nothing in Phases 1–4 should have to change to add these. +The last work before the first release. It was sketched as four items — `.deb` packaging, Windows +MSI, arm64 cross build, optional automated backup before upgrade — and the org lead settled its scope +on 2026-08-05: **two of the four are dropped rather than deferred**, because what stops them is an +ownership conflict that does not improve with time, and two are built. -**It now runs before the `edge → main` cutover rather than after it** (org lead, 2026-08-05). The -original order assumed the cutover would cut a v1 and packaging would follow as a v1.x — but this -phase changes the *release layout* (§3), so shipping first would mean a first release that is -superseded by the next one, and operators who downloaded a bare binary being told to re-download a -package. Deferring the cutover costs nothing: nothing is published from `edge`, and the guide's -Appendix A is the supported path meanwhile. +**It runs before the `edge → main` cutover rather than after it.** The original order assumed the +cutover would cut a v1 and packaging would follow as a v1.x — but this phase changes the *release +layout* (§3), so shipping first would mean a first release that is superseded by the next one, and +operators who downloaded a bare binary being told to re-download a package. Deferring the cutover +costs nothing: nothing is published from `edge`, and the guide's Appendix A is the supported path +meanwhile. -The two entry criteria for the cutover are therefore this phase and the outstanding **Windows SCM +| Item | Decision | +|---|---| +| `.deb` for uo-link | **Dropped** (§5.1) | +| Windows MSI | **Dropped** (§5.1) | +| arm64 cross build | **Build** — Linux `aarch64`, both components (§5.2) | +| Backup before upgrade | **Build** — on by default, scoped to what a run overwrites (§5.3) | + +#### 5.1 What ships is plain binaries: no `.deb`, no MSI + +Both were proposed before Phases 2 and 4 existed. They now collide with the components those phases +made the installer own, and the collision is the deciding argument in each case. + +- **A `.deb` under link's release would own `/usr/bin/runicgateway-link`, the systemd unit and the + service user** — the same three things `service.rs` writes, hardens and removes, and that + `install.json` records so `doctor` and `uninstall` can reason about them. Two owners for one file + is not a packaging detail: `uninstall` deleting a dpkg-owned binary leaves the package *installed* + and broken, an `apt upgrade` replacing that binary makes `doctor` report drift no operator caused, + and the unit text would live in two repos free to disagree about the account it runs as. The + variant that avoids all of that — a binary-only `.deb`, no unit, no user — buys apt-managed + upgrades of one file, which `update` already does from a **protocol-checked** bundle (§7.1). That + is the stronger of the two guarantees, so the package would be trading correctness for + familiarity. +- **An MSI contradicts a decision already locked**: the installer does not install itself (Phase 0.4 + as built). It would also add a second uninstall path — Add/Remove Programs — beside the + `uninstall` verb, which owns state an MSI cannot see: `install.json`, the cached patch set and its + pre-images, and the ServUO-tree report that is printed rather than done (Phase 4). Unsigned, it + raises the same SmartScreen prompt a bare `.exe` does (§3), so it does not even buy the dialog it + looks like it should. + +So §3's release layout loses its `.deb` line and gains no installer package. Nothing in Phases 1–4 +changes — this item is a removal, which is why settling it is cheap and shipping it first would not +have been. + +**What would reopen it.** If operators ask for `apt`- or `winget`-managed installs, the thing to +package is the sidecar as a standalone service *with the installer taught to detect and defer to a +package-managed one*. That is a design change to who owns the service, not packaging polish, and it +is out of scope here. + +#### 5.2 arm64 — Linux `aarch64`, both components + +§2.6 recorded arm64 as "not buildable today", which was true of the *workflows* rather than of Rust: +`link/release.yml` and `installer/release.yml` cross-compile `x86_64` Linux and Windows only, so +`platform_key()` (`src/bundle.rs`) refuses every other host by name. Ampere/Graviton instances and +Pi-class boxes are a realistic ServUO home, and the installer's dependency set was already chosen to +avoid OpenSSL and any C toolchain of its own (Phase 1 as built) — which is what makes this two build +steps per workflow rather than a toolchain project. + +**Both components or neither.** An installer that runs on `aarch64` but resolves a bundle carrying no +`aarch64` sidecar has moved the failure later, not fixed it. Windows-on-arm and macOS stay unbuilt: +no supported MinGW target here for the first, and no shard host is the second. + +**The order is forced by the bundle CI, which is strict on purpose** (Phase 0.3 as built): an +unrecognized `link` asset name is a hard failure, and `linux-x86_64` + `windows-x86_64` are asserted +present. So a link release carrying a new binary reddens the compose job unless it is taught the name +first — and requiring the name before link publishes it fails *every* bundle for as long as the gap +lasts. Hence: + +| Step | Repo / branch | Change | Why it is this one first | +|---|---|---|---| +| 1 | `installer` `main` | `bundle.yml` maps `*-linux-aarch64` → `linux-aarch64`, and **does not** add it to the required list | The compose job runs from `main`; teaching it the name first means link's next release composes instead of failing | +| 2 | `link` `main` | `release.yml` adds `aarch64-unknown-linux-gnu`, cross-linked with `gcc-aarch64-linux-gnu` | Publishes the first `aarch64` sidecar; the nightly cron folds it into a bundle | +| 3 | `installer` `main` | `linux-aarch64` joins the required list | Only safe once a release actually carries it — from here a dropped target reddens CI instead of silently vanishing from every bundle | +| 4 | `installer` `edge` | `release.yml` adds the same target; `platform_key()` learns `("linux", "aarch64")` | The installer binary itself, on the branch that carries the crate | + +`bundle.yml` is therefore edited on `main` only and never on `edge`, so the cutover merge has nothing +to conflict over. + +#### 5.3 Backup before overwrite + +**Scoped by what cannot be fetched again.** Not the sidecar binary or the overlay files — both are +re-downloadable and hash-named in the bundle. Not the database either: `link/sidecar/src/store.rs` +creates every table `IF NOT EXISTS` and every one of them holds shard state the sweeps repopulate, so +it is a cache with a schema rather than a record. What an `update` can destroy irrecoverably is: + +1. **An operator's own edits to a deployed `.cs` file.** Phase 1 overwrites those unconditionally and + by design — `Bridge.cfg` is the single exception — so the one place the tool knowingly discards + work is the one place it should keep a copy. +2. **`sidecar.toml`**, whose token the website already holds. Mint a new one and the site's saved + config starts answering `409`/`401` with nothing on the sidecar to explain why (§2.4). + +So the rule is **copy what this run is about to overwrite, plus `sidecar.toml`** — not a snapshot of +everything. A snapshot of all 24 overlay files would be mostly byte-identical to a release tarball +that is still downloadable, and the noise would bury the two files that matter. + +- **Where:** `/backups//`, each file mirroring its path in the tree it came + from, beside a `manifest.json` naming the source path, its SHA256, and the bundle moved from and + to. In the state directory, not the ServUO tree — the same rule the tier's `patches/originals/` + follows, and for the same reason: `uninstall` has promised never to clean up in there. +- **When:** on by default for `update`, and for an `install` over an existing record. A first + install overwrites nothing and writes no backup. `--verify` writes none either, for the reason it + runs no part of the sidecar half (Phase 2): a dry run must not create state. +- **`--no-backup`** opts out, for an operator with their own snapshotting. +- **Retention is three.** Older ones are pruned as new ones are written; an unbounded directory of + ServUO source copies on a shard host is its own support problem. `uninstall` keeps them and + `--purge` drops them, alongside the config, the database and the cached patch set — same rule as + Phase 4, and the same reason: they are the only offline record of what was there before. +- **Restore is printed, not done.** Consistent with the uninstall report, and for the identical + reason: the installer cannot know what has changed since, and a clever automatic restore over a + newer overlay eats work rather than saving it. `doctor` names the most recent backup and its + timestamp, so an operator asking "can I go back" does not have to know the layout to answer. + +#### 5.4 Documentation the release layout invalidates + +Not optional, and grouped here because a first release is the moment these are read for the first +time by someone who was not in the room: + +- **`installer/README.md`'s status table still says Phase 1 is built and Phase 2 is next**, four + phases later. It is the first thing a visitor to the repo reads. +- **`INSTALL.md`** gains the `aarch64` download lines, the backup behaviour and `--no-backup`. +- **This file:** §3 loses the `.deb` and gains the `aarch64` assets, §2.6's arm64 bullet is now + historical, and §7.1's example bundle grows the third asset key. + +#### Cutover entry criteria + +The `edge → main` cutover is gated on this phase **and** on the outstanding **Windows SCM verification** (see the status header and [Phase 2 as built](#phase-2--uo-link-install-and-service)). The second is not busywork: running the systemd half for real found a bug that unit tests could not, -and `sc create` remains the only code in the crate that has never executed. +and `sc create` remains the only code in the crate that has never executed. The smoke script at the +workspace root was run on 2026-08-05 and stopped at its first check — an unelevated shell — so +nothing in that half has executed yet; it needs one run from an elevated PowerShell. --- @@ -1016,6 +1139,7 @@ resolving "latest", CI publishes a small manifest naming an exact, checked combi "repo": "RunicGateway/link", "tag": "v1.1.0", "version": "1.1.0", "protocol": 3, "assets": { "linux-x86_64": { "name": "uo-link-sidecar-linux-x86_64", "url": "…", "sha256": "27d491ef…" }, + "linux-aarch64": { "name": "uo-link-sidecar-linux-aarch64", "url": "…", "sha256": "…" }, "windows-x86_64": { "name": "uo-link-sidecar-windows-x86_64.exe", "url": "…", "sha256": "fbefd886…" } } }, @@ -1030,7 +1154,8 @@ resolving "latest", CI publishes a small manifest naming an exact, checked combi Note `link.assets` is a **map keyed by platform**, not the single `sha256` this section originally sketched: link publishes a Linux binary and a Windows `.exe`, and the installer runs on both, so one -hash could only ever have described one of them. `schema` versions this document's shape and is +hash could only ever have described one of them. Phase 5's `linux-aarch64` (§5.2) is the first +addition that map was shaped for, and it costs the bundle nothing but a key. `schema` versions this document's shape and is independent of `protocol` and of either component's release version — all three move separately. The installer fetches the current bundle at run time; `--bundle ` pins an older one for a From e18eec8957b3d0920baf02c91315464f160e3d79 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 5 Aug 2026 05:49:09 -0500 Subject: [PATCH 2/3] =?UTF-8?q?docs(installer):=20correct=20=C2=A75.3's=20?= =?UTF-8?q?trigger=20and=20record=20what=20building=20=C2=A75.2=20found?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things this section got wrong, both found by implementing it. §5.3 said a backup is taken for an update and for an install over an existing record, because "a first install overwrites nothing". That is not true of a tree deployed by hand per INSTALL.md Appendix A2 -- the path this project recommends while the binary is unreleased. There the first install finds .cs files that differ, plans them as Change, and overwrites them with no prior record anywhere to notice. The rule is now the direct one: back up whenever the run is about to overwrite something. A genuine first install onto a clean tree still writes nothing, because there is nothing to copy. Also records that sidecar.toml joins a backup rather than causing one (nothing rewrites it, so triggering on it would leave a dated directory after every no-op update), and that the directory is created lazily with the manifest written last, so an interrupted run can neither be mistaken for a backup nor evict a good one. §5.2 gains what its cross-builds turned up: neither crate builds with the arm64 compiler alone. gcc-aarch64-linux-gnu only recommends libc6-dev-arm64-cross while both release workflows install with --no-install-recommends, so the C in each crate -- bundled SQLite under sqlx, ring under ureq's rustls -- fails on a missing libc header while every Rust dependency compiles fine. Co-Authored-By: Claude --- installer/PLAN.md | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/installer/PLAN.md b/installer/PLAN.md index d2edf7d..2b068c0 100644 --- a/installer/PLAN.md +++ b/installer/PLAN.md @@ -894,12 +894,13 @@ operators who downloaded a bare binary being told to re-download a package. Defe costs nothing: nothing is published from `edge`, and the guide's Appendix A is the supported path meanwhile. -| Item | Decision | -|---|---| -| `.deb` for uo-link | **Dropped** (§5.1) | -| Windows MSI | **Dropped** (§5.1) | -| arm64 cross build | **Build** — Linux `aarch64`, both components (§5.2) | -| Backup before upgrade | **Build** — on by default, scoped to what a run overwrites (§5.3) | +| Item | Decision | State | +|---|---|---| +| `.deb` for uo-link | **Dropped** (§5.1) | — | +| Windows MSI | **Dropped** (§5.1) | — | +| arm64 cross build | **Build** — Linux `aarch64`, both components (§5.2) | In review: [installer#9](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/9), [link#26](https://gitea.whitlocktech.com/RunicGateway/link/pulls/26), [installer#10](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/10); step 3 waits on link's first arm64 release | +| Backup before upgrade | **Build** — on by default, scoped to what a run overwrites (§5.3) | In review: [installer#11](https://gitea.whitlocktech.com/RunicGateway/installer/pulls/11) | +| Docs the release invalidates | **Do** (§5.4) | Pending | #### 5.1 What ships is plain binaries: no `.deb`, no MSI @@ -961,6 +962,13 @@ lasts. Hence: `bundle.yml` is therefore edited on `main` only and never on `edge`, so the cutover merge has nothing to conflict over. +**What building it found.** Neither crate cross-compiles with the arm64 *compiler* alone. +`gcc-aarch64-linux-gnu` only **recommends** `libc6-dev-arm64-cross`, and both release workflows +install with `--no-install-recommends` — so the C that each crate pulls in (bundled SQLite under +`sqlx` for the sidecar, `ring` under `ureq`'s rustls for the installer) dies on a missing +`bits/libc-header-start.h` while every Rust dependency builds fine. Both halves were reproduced in a +`rust:1-slim-bookworm` container, failure then fix, before either workflow was written. + #### 5.3 Backup before overwrite **Scoped by what cannot be fetched again.** Not the sidecar binary or the overlay files — both are @@ -982,14 +990,28 @@ that is still downloadable, and the noise would bury the two files that matter. from, beside a `manifest.json` naming the source path, its SHA256, and the bundle moved from and to. In the state directory, not the ServUO tree — the same rule the tier's `patches/originals/` follows, and for the same reason: `uninstall` has promised never to clean up in there. -- **When:** on by default for `update`, and for an `install` over an existing record. A first - install overwrites nothing and writes no backup. `--verify` writes none either, for the reason it - runs no part of the sidecar half (Phase 2): a dry run must not create state. +- **When: whenever the run is about to overwrite something**, whichever verb was typed. This + section first said "`update`, and `install` over an existing record", justified by a first install + overwriting nothing — **which is wrong**, and building it is what showed that. A tree deployed by + hand per [`INSTALL.md`](INSTALL.md) Appendix A2 — the path this project recommends while the + binary is unreleased — has `.cs` files the first `install` plans as `Change` and overwrites, with + no prior record anywhere to notice. The direct test covers that case, and a genuine first install + onto a clean tree still writes nothing, because there is nothing to copy. `--verify` writes none + either, for the reason it runs no part of the sidecar half (Phase 2): a dry run must not create + state. +- **`sidecar.toml` joins a backup that is already being taken; it is never the reason for one.** + Nothing in the installer rewrites it, so treating it as a trigger would leave a dated directory + behind after every no-op `update` — the empty-backup problem one step along. It is copied so that + a restored set of files arrives with the token that matches it. - **`--no-backup`** opts out, for an operator with their own snapshotting. - **Retention is three.** Older ones are pruned as new ones are written; an unbounded directory of ServUO source copies on a shard host is its own support problem. `uninstall` keeps them and `--purge` drops them, alongside the config, the database and the cached patch set — same rule as Phase 4, and the same reason: they are the only offline record of what was there before. +- **The directory is created lazily and the manifest is written last**, so a directory carrying one + is a *complete* backup. Listing and pruning consider only those: a run interrupted mid-copy must + neither be mistaken for a backup nor be able to evict a good one by being newer than it. It is + left on disk for a human to look at rather than silently deleted. - **Restore is printed, not done.** Consistent with the uninstall report, and for the identical reason: the installer cannot know what has changed since, and a clever automatic restore over a newer overlay eats work rather than saving it. `doctor` names the most recent backup and its From ad7defd4710329376c1c45e1e6dcf06ccfde41a8 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 5 Aug 2026 05:53:11 -0500 Subject: [PATCH 3/3] docs(installer): document aarch64 and the upgrade backup in INSTALL.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PLAN.md §5.4. The operator guide is the specification of the run, so these are part of building the phase rather than a write-up after it. - The download list gains runicgateway-installer-linux-aarch64, with `uname -m` as the way to tell, and says plainly that there is no macOS and no Windows-on-arm build: the shard dials the sidecar out on loopback, so the two share a host, and no ServUO host is either. - Appendix A3 names the arm64 sidecar asset for the by-hand path, and points at the bundle from A1 for the version rather than the one written in the example. - §7's `update` says what a backup is, when one is taken and when one is not, and that restoring is the operator's to do -- the guide already promised their Bridge.cfg edits survive, and this is the same promise for the .cs file they edited that gets overwritten by design. - --no-backup joins the flags table; --purge's row and the uninstall table now name backups alongside the config, the database and the cached patch set. - The paths tables and doctor's sample output gain the backups directory and its row. Co-Authored-By: Claude --- installer/INSTALL.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/installer/INSTALL.md b/installer/INSTALL.md index a9b379c..65a198c 100644 --- a/installer/INSTALL.md +++ b/installer/INSTALL.md @@ -83,10 +83,16 @@ Download the installer for your OS, plus `SHA256SUMS`, from the ``` runicgateway-installer-linux-x86_64 +runicgateway-installer-linux-aarch64 runicgateway-installer-windows-x86_64.exe SHA256SUMS ``` +`linux-aarch64` is for arm64 hosts — Ampere/Graviton instances, Pi-class boxes. `uname -m` says +`aarch64` on those and `x86_64` otherwise. There is no macOS build and no Windows-on-arm build: the +shard dials the sidecar out on loopback, so the two have to share a host, and no ServUO host is +either of those. + **Linux** ```bash @@ -218,7 +224,8 @@ reports "unchanged" and writes nothing. | `--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. **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. | +| `--no-backup` | `install`, `update` | Do not copy the files this run is about to overwrite. They are otherwise saved under the state directory — see [§7](#7-day-two). | +| `--purge` | `uninstall` | Also delete `sidecar.toml`, `uo-link.db`, the cached patch set and every backup, 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: @@ -238,6 +245,7 @@ else still was). | `/etc/runicgateway/install.json` | What the installer deployed: versions, commit, per-file hashes, applied patches, timestamps | | `/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 | +| `/etc/runicgateway/backups//` | Copies of the files a run replaced, with a `manifest.json` naming each. Newest three kept; skip with `--no-backup` | | `/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 | @@ -250,6 +258,7 @@ else still was). | `%ProgramData%\RunicGateway\install.json` | As above | | `%ProgramData%\RunicGateway\patches\` | As above | | `%ProgramData%\RunicGateway\patches\originals\` | As above | +| `%ProgramData%\RunicGatewayackups\\` | As above | | `%ProgramData%\RunicGateway\uo-link.db` | The sidecar's SQLite store | | Service `RunicGatewayLink` | Automatic start, restart on failure, running as `NT SERVICE\RunicGatewayLink` | @@ -520,6 +529,8 @@ first thing a maintainer will want. ✓ Protocol sidecar 3 = overlay manifest 3 ✗ Shard connected no — the shard is running (pid 8123) but has not dialed in ✓ Bundle 2026.08.04 — up to date +✓ Backups 2026-08-04T09:12:44Z — 3 file(s) replaced by update to bundle 2026.08.04 + 3 kept in /etc/runicgateway/backups ``` Rows come from asking the installed sidecar (`--version`, `--print-config`) rather than from reading @@ -557,6 +568,16 @@ 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. +**Anything it does overwrite is copied first.** Every `.cs` file the overlay owns is replaced +unconditionally — that is deliberate, they are code — so if you have edited one, the run saves your +copy under `backups//` in the state directory before writing, alongside `sidecar.toml` +and any stock ServUO file the patch tier is about to touch. Each backup carries a `manifest.json` +saying where every file came from. The newest three are kept; `--no-backup` skips taking one. + +Putting a file back is yours to do — the installer will not restore an old file over a newer +release, because it cannot know what has changed since. A run that overwrites nothing takes no +backup, so a no-op `update` leaves nothing behind. + 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. @@ -581,7 +602,7 @@ your work. | | | |---|---| | **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) | +| **Kept** | `sidecar.toml`, `uo-link.db`, the cached patch set with its pre-patch originals, and every backup an upgrade took (`--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. | @@ -679,6 +700,10 @@ for the *comparison* while writing back your file's own endings, which is why it ### A3. Install the sidecar +On an arm64 host substitute `uo-link-sidecar-linux-aarch64` for the asset name below (`uname -m` +says `aarch64`); releases from v1.2.0 carry both. Take the version from the bundle you fetched in +A1 rather than the one written here. + ```bash curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/uo-link-sidecar-linux-x86_64 curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/SHA256SUMS