docs(installer): Phase 5 — scope, the §5.3 correction, and the operator guide #96
@@ -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_<ver>_amd64.deb (Phase 5)
|
||||
├── uo-link-sidecar-linux-aarch64 (Phase 5)
|
||||
└── SHA256SUMS
|
||||
|
||||
servuo-plugins v<ver> (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:** `<state>/backups/<utc-timestamp>/`, 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 <tag>` pins an older one for a
|
||||
|
||||
Reference in New Issue
Block a user