wtclaude 82900da939
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m59s
feat(installer): back up what a run is about to overwrite
PLAN.md §5.3. Before anything is written, every file this run will
replace is copied into <state>/backups/<utc-stamp>/ with a manifest
naming where each came from. --no-backup opts out; --verify takes none.

Scoped by what cannot be fetched again. The sidecar binary and the
overlay files are re-downloadable and hash-named in the bundle, and the
database is a cache with a schema -- link's store.rs creates every table
IF NOT EXISTS over shard state the sweeps repopulate. What a run can
destroy for good is an operator's edits to a deployed .cs file, which
Phase 1 overwrites unconditionally and by design, and sidecar.toml,
whose token the website already holds.

Two deviations from §5.3 as written, both found by building it:

- The trigger is "this run is about to overwrite something", not "an
  update, or an install over an existing record". §5.3 justified the
  latter with "a first install overwrites nothing" -- which is not true
  of a tree deployed by hand per INSTALL.md Appendix A2, a documented
  path. There the first install finds .cs files that differ, plans them
  as Change, and overwrites them with no record anywhere. The direct
  test covers that case and still writes nothing for a genuine first
  install, because there is nothing to copy.
- sidecar.toml joins a backup that is already being taken and is never
  the reason for one. Nothing here rewrites it, so making it a trigger
  would put a dated directory on disk after every no-op update; it is
  copied so a restored set of files comes with the token that matches
  them.

The directory is created lazily and the manifest is written last, so a
directory carrying one is a complete backup -- and pruning only
considers those, so a run interrupted mid-copy cannot evict a good
backup by being newer than it. Three are kept. uninstall keeps them and
names them in its report; --purge removes them, alongside the config,
the database and the cached patch set. doctor reports the newest.

Restoring stays printed rather than done, as the uninstall report is:
the installer cannot know what has changed since, and putting an old
.cs file back over a newer overlay eats work rather than saving it.

Verified live against two scratch ServUO trees built from the real 57.4
files: a clean first install leaving no backups directory at all, an
update after editing a deployed .cs (copy holds the edit, tree gets the
release's file, manifest lists both it and sidecar.toml), a no-op update
taking none, --no-backup and --verify each taking none, a fourth backup
pruning the oldest, doctor's row, uninstall keeping three and listing
them, --purge removing them, and a --patches run capturing the
pre-patch Logging.cs while the two rung-0 patches correctly captured
nothing. fmt, clippy -D warnings and 144 tests on both Linux and
Windows.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 05:47:25 -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.

Status

Phase 1 (installer core) is built, on the edge branch. Nothing is released yet.

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).

Phase State
0 — prerequisites in the other repos merged
1 — installer core: bundle resolution, ServUO detection, overlay sync, install.json on edge
2 — uo-link install + service registration next
3 — the opt-in stock-file patch tier
4 — doctor, update, uninstall

Why edge: release.yml publishes an installer binary on every push to main, and a binary that deploys the overlay but cannot yet install the sidecar is not something to hand an operator. Phases 1 and 2 land on edge; the edge → main cutover cuts the first release. PRs into edge run the same gates as PRs into main.

Until then, the way to install is by hand — INSTALL.md Appendix A is the same deployment done with curl, tar and systemctl.

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.

Planned 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.
  • 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 install.json (normally /etc/runicgateway or %ProgramData%\RunicGateway), which is how a 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 592 KiB
Languages
Python 100%