The first commit on this branch said the plan step "CAN recover an orphan, but
only on a run that reaches it". Checking link and servuo-plugins for the same
gaps showed that understated it.
The recovery is VERSION-SCOPED. It computes VERSION from the newest tag plus the
conventional-commit bump, then only checks refs/tags/v${VERSION}. So it recovers
an orphan on the very next run and is useless afterwards: once any releasable
commit lands, the next run computes a NEW version and never looks at the old tag
again. The orphan becomes permanent and silent.
servuo-plugins proved it, and the proof is pointed. Its v0.1.0 had been orphaned
since 2026-08-04 -- tag present, no release, no assets -- while v0.1.1, v0.2.0
and v1.0.0 all published normally. The commit that ADDED the recovery to that
repo was itself typed "fix(release): preflight credentials and recover the
orphaned v0.1.0 tag", so it bumped to v0.1.1, and the run that introduced the
recovery stepped straight past the tag it was written to rescue.
The retry added in the previous commit makes an orphan much less likely, but it
does not make one impossible -- a cancelled job or a dying runner produces the
same state with no 500 anywhere -- and until now nothing would ever have
mentioned it again.
So the plan step now sweeps every v* tag and warns about any without a release.
It WARNS rather than recovers, on the org lead's decision. Publishing an old
version would mean building today's tree and shipping it under a tag whose tree
it is not, which is worse than the inconsistency it fixes; and a routine push
silently republishing ancient history is not a thing this pipeline should be
able to do. Recovery stays limited to the version the run computed.
It also never fails the run. A sweep that can break a good release is a sweep
someone will delete.
Verified by running the loop against the real repositories rather than a stub,
since the only thing worth proving is that it tells a clean repo from a dirty
one:
link (9 tags): clean
servuo-plugins (4 tags): :⚠️:Tags with no release: v0.1.0
installer (2 tags): clean
and again after servuo-plugins#15 deleted that tag, where all three report
clean. Every run block bash -n clean, the YAML parses, and no empty template
token.
Companion PRs: link#33 and servuo-plugins#15.
Co-Authored-By: Claude <noreply@anthropic.com>
Runic Gateway installer
A single-binary deployment tool that takes a stock ServUO installation and configures it for Runic Gateway: deploys the shard plugin overlay, optionally applies the stock-file patch tier, installs the uo-link sidecar and registers it as a service, records what it deployed, and hands the operator the four values that connect the website to the shard.
┌──────────────────────────────────────────┐
│ Runic Gateway installer (>>> HERE <<<)│
└───────────────┬──────────────────────────┘
│ deploys
┌───────────────┴────────────────┐
▼ ▼
ServUO integration uo-link sidecar
overlay sync + opt-in patch tier binary + config + service
(RunicGateway/servuo-plugins) (RunicGateway/link)
It is a deployment tool, not a hosted bootstrapper — no curl | bash, no
installer service. Artifacts are downloaded from a Gitea release page and run.
It also does not replace ServUO startup behavior. ServUO keeps running through its existing release/start scripts; the installer never writes a launcher and never restarts the shard.
Install a shard with it
Grab a binary and SHA256SUMS from the
releases page,
verify the checksum, and run it as Administrator/root against a stopped shard:
sha256sum -c SHA256SUMS --ignore-missing
chmod +x runicgateway-installer-linux-x86_64
sudo ./runicgateway-installer-linux-x86_64 install
# Windows, from an elevated PowerShell
.\runicgateway-installer-windows-x86_64.exe install
It ends by printing the four values to paste into Admin → Shard on your site.
The full operator guide — what it asks, where it writes, the patch tier, day-two
commands and troubleshooting — is
installer/INSTALL.md.
Prefer to place everything yourself, or on a host that cannot run the binary?
INSTALL.md Appendix A
is the same deployment done with curl, tar and systemctl, and stays
supported.
Status
Released. All five phases are built and the edge → main cutover (#17) cut
the first release, v0.1.0,
publishing linux-x86_64, linux-aarch64 and windows-x86_64.exe with
SHA256SUMS.
| Phase | State |
|---|---|
| 0 — prerequisites in the other repos | ✅ merged |
1 — installer core: bundle resolution, ServUO detection, overlay sync, install.json |
✅ released |
| 2 — uo-link install + service registration | ✅ released |
| 3 — the opt-in stock-file patch tier | ✅ released |
4 — doctor, update, uninstall |
✅ released |
5 — packaging polish: Linux aarch64, backup before overwrite |
✅ released |
The binary does everything
installer/INSTALL.md
describes: bundle resolution, ServUO detection and validation, the overlay sync,
the opt-in patch tier, install.json, the uo-link sidecar and its service, the
token handoff, and doctor / update / uninstall.
The design of record is
installer/PLAN.md
in the docs repo: phases, locked decisions, and the Phase 0 prerequisites in other
repos (a servuo-plugins release workflow, a non-interactive config read-back in
link, and the bundle-manifest CI here), all of which have landed —
bundles/current.json
names the current protocol-checked sidecar + overlay combination, recomposed on
every component release and nightly (see bundles/README.md).
main publishes. release.yml cuts a release from every push to main, which
is why the crate was integrated on edge until it was worth handing to an
operator. Both cutover gates were met first: Phase 5 (its scope settled as no
.deb and no MSI — either would give the sidecar binary, its service unit and
its service account a second owner beside this tool), and the Windows SCM half
verified on a real host. That second one earned its place: sc start failed
with 1053 on its first real run and needed a sidecar fix (link#29) before it
passed 13/13.
Related repos
| Repo | What |
|---|---|
this — RunicGateway/installer |
The installer (Rust, one binary per OS). |
| RunicGateway/link | The uo-link sidecar — the network-facing half of the game bridge. Installed and service-registered by this tool. |
| RunicGateway/servuo-plugins | The C# ServUO plugin — deployed as source (overlay/) and compiled by ServUO at boot. Synced into the server tree by this tool. |
| RunicGateway/website | The public site and admin panel. The installer never contacts it — it prints values for Admin → Shard. |
| RunicGateway/docs | All project documentation, including the installer plan. |
Commands
| Command | What it does |
|---|---|
install |
Detect and validate the ServUO root, sync the overlay, optionally apply patches, install uo-link + service, write install.json, print the token handoff. |
doctor |
Diagnose an installed deployment end to end — through to "has a shard actually dialed in?", the only check that distinguishes a working bridge from copied files. |
update |
Resolve the current bundle manifest, then update the sidecar (replace + restart) and the overlay (re-sync + tell the operator to restart ServUO). |
uninstall |
Remove only what the installer exclusively owns. It never edits the ServUO tree — it prints the overlay files to delete and the patch hunks to revert, and leaves that call to the operator. |
Design constraints worth knowing up front
- Releases are unsigned.
SHA256SUMSis the trust anchor; SmartScreen and Gatekeeper warnings are expected and documented. The installer nonetheless verifies the SHA256 of everything it downloads and refuses on mismatch. - Composition comes from a published bundle manifest, not from "latest of
each". CI names an exact, protocol-checked combination of sidecar and overlay
versions;
--bundle <tag>pins one for a reproducible install. A new component release regenerates JSON, not this binary. - The base install must complete without the patch tier. The patch tier edits stock ServUO files, most real shards are hand-modified, and unverified ServUO versions skip it with a warning rather than being patched blind.
- A successful copy is not a working bridge. ServUO ignores the script build's
exit code and silently reloads the previous
Scripts.dll, so diagnostics verify post-boot state rather than trusting a clean boot. - What a run overwrites is copied first. Every
.csfile the overlay owns is replaced unconditionally, so an operator's edit to one is saved underbackups/<timestamp>/before it goes. Restoring is theirs to do — this tool will not put an old file back over a newer release. - The audience is public — any ServUO operator, not only shards we run.
Build & run
A standard cargo project, with the crate at the repo root:
cargo build --release
cargo run -- --help
cargo run -- install --servuo /path/to/ServUO --verify # dry run: writes nothing
cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test
RUNICGATEWAY_STATE_DIR relocates everything the installer writes — state,
data, and the sidecar binary (normally /etc/runicgateway, /var/lib/runicgateway
and /usr/bin, or %ProgramData%\RunicGateway and %ProgramFiles%\RunicGateway).
It also suppresses service registration, since there is no such thing as a
relocated systemd unit or Windows service. That is how a full run is tested
without root.
Two layout notes that look odd until you know why:
- The library target is
rgdeploy, notrunicgateway_installer. Windows' UAC installer detection refuses to launch an unsigned executable whose file name containsinstall(os error 740), and Cargo names test harnesses after their target — so a target under that name makescargo testunrunnable on Windows. The published binary keeps its documented name;[[bin]] test = falsekeeps Cargo from building a harness under it. Expect a UAC prompt when running the built binary on Windows; it needs Administrator anyway. Cargo.lockis committed, and CI builds--locked.
See CONTRIBUTING.md for the development setup, the local checks CI will run, and the branch/PR workflow.
License
Runic Gateway is free software, licensed under the GNU General Public License v3.0 or later — see LICENSE.md.
Copyright (C) 2026 Runic Gateway
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version. It is distributed WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
Contributions are welcome — please read CONTRIBUTING.md (note the AI-usage disclosure requirement) and our Code of Conduct. Report vulnerabilities privately per SECURITY.md.