wtclaude 188e6eb882
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m32s
ci(release): show the error body, and retry the release POST
Run 75 built every artifact, pushed tag v0.1.1, then took a 500 from
POST /releases one second later and exited 22. The tag was left orphaned with
no release and no binaries, so the handoff fix in #22 reached no operator until
the workflow was re-run by hand today.

Re-running published the same four assets untouched, via the orphan-tag
recovery the plan step already has. So the 500 was a race with the tag push --
Gitea had not finished processing the pushed tag when the POST arrived -- and
not a bad request.

Two separate gaps made that worse than it needed to be.

`curl -sSf` prints no response body on an error status. All the log carried was
"curl: (22) ... error: 500", so the cause had to be inferred from timestamps
rather than read. Every call in this step now captures the body and prints it
on failure, including the asset uploads.

And nothing retried. The plan step can recover an orphan tag, but only on a run
that reaches it, and a later push with no releasable commits stands down before
it gets there -- which is why this one sat until someone looked. The POST now
retries five times with a 5/10/15/20s backoff.

4xx is deliberately not retried: a bad token or a malformed body will not
improve by being sent again, and retrying would turn a clear failure into a
slow one. A give-up message names the orphan tag and says a re-run republishes.

The asset uploads get the same treatment, because a release whose SHA256SUMS
does not cover every binary it advertises is worse than no release -- that file
is the trust anchor for an unsigned download.

Verified by extracting the step's shell from the YAML and running the loop
against a stubbed curl: first-try success, 500-then-success (the case that
actually happened), two 500s then success, five 500s giving up, 403 and 404
aborting without retrying, and a 000 network failure being retried. bash -n
clean and the YAML parses.

Typed ci(...) rather than fix(...) on purpose: the plan step bumps on feat/fix,
and this changes no binary, so a release here would be an empty one.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 11:54:47 -05:00

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.

Repo What
thisRunicGateway/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. SHA256SUMS is 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 .cs file the overlay owns is replaced unconditionally, so an operator's edit to one is saved under backups/<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, not runicgateway_installer. Windows' UAC installer detection refuses to launch an unsigned executable whose file name contains install (os error 740), and Cargo names test harnesses after their target — so a target under that name makes cargo test unrunnable on Windows. The published binary keeps its documented name; [[bin]] test = false keeps Cargo from building a harness under it. Expect a UAC prompt when running the built binary on Windows; it needs Administrator anyway.
  • Cargo.lock is 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.

Description
No description provided
Readme 726 KiB
v0.2.0 Latest
2026-09-14 23:11:53 +00:00
Languages
Rust 99.6%
Python 0.4%