Module-rust phase 18, step 5 of docs/modules/rust/PLAN.md §34.2.7 (D146,
D148, D149, D153).
Bundles: ServUO is read at schema 2 from v2/servuo/ and lowered into the
schema-1 model. Schema 1 at the root is the fallback, so a pin from before
schema 2 still reproduces. Rust bundles are read from v2/rust/. v2 reads use
the contents API, because /raw/ is CDN-cached for six hours.
--game rust runs install, update, doctor and uninstall for Rust servers
(src/rustgame/):
- the framework is detected from its marker files, which were read off both
rigs; both or neither is refused;
- --server-id names an instance: its own service (runicgateway-rust@<id>, or
RunicGatewayRust-<id>), config, database and ports;
- the plugin config is written once, with ServerId and Port only. An existing
one is never rewritten, and one naming another server refuses the run;
- each instance's sidecar.toml is written once with its ports and an absolute
database path, and the sidecar generates the token into it;
- one binary per host. update moves every instance, and a replaced binary
restarts all of them;
- doctor checks the plugin file hash, the plugin config's ServerId, the
required uMod plugins (a warning), the service and /health, and passes when
the plugin is connected;
- uninstall removes our plugin and keeps its config. --purge also removes the
sidecar config and database. The last instance takes the binary, the
template and the record, and the shared user only when no ServUO record
remains.
service.rs takes the service name as a parameter internally. The ServUO
public API is unchanged.
Finding: Carbon 2.0.259's config.json has no folder keys, so carbon/plugins
and carbon/configs are what the installer uses. The plan expected a moved
directory to be readable there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
`main` is protected, so the compose job cannot push a bundle there --
the pre-receive hook declines it, which is not something a nightly cron
can resolve. Bundles now publish to a branch of their own, at its root,
so BUNDLE_BASE follows them. Everything the original choice was for
survives the move: a reviewable diff, a git history of the compat
matrix, and a plain anonymous URL that needs no credentials on the
shard host.
The test's bundle is now a frozen fixture rather than an include of the
published file, which this checkout no longer carries. Frozen is the
honest shape anyway: a test that silently re-targeted whatever CI
published last would change meaning without a commit. It is still a
real CI-emitted document, copied verbatim.
Nothing is released from `edge`, so no shipped binary ever read the old
URL.
Co-Authored-By: Claude <noreply@anthropic.com>
Two features need edits to stock ServUO sources, because the events they
depend on do not exist. This adds the rung ladder of PLAN.md §2.2.1, the
unsupported-version path of §2.2.2, and the record and cache Phase 4 will read.
Three decisions were not settled by the plan:
* The engine is fully native, with no `git`. §2.2.1 wrote rung 1 as "apply
verbatim with git apply", but §1 chose the release tarball specifically so
there would be no git on the shard host, and rung 2 needs a native applier
regardless. Rung 1 keeps its distinct, stronger verdict — the whole file
reproduced the diff's `index` pre-image, computed as a git blob SHA1 in
process — while the write goes through the same code path as rung 2. On the
real trees here that is not academic: the shipped .patch files are CRLF in a
Windows checkout and two of their three targets are LF, so `git apply`
refuses patches this applies correctly.
* Per-patch metadata is declared by the release, with a built-in fallback.
Which patches form one all-or-nothing unit, which companion .cs follows
which, whether a CORE rebuild is needed and what declining costs are not
derivable from a diff. servuo-plugins now declares them; overlay v0.1.1 is in
the current bundle and declares nothing, so a built-in copy stands in for it.
A checked-in fixture of the release workflow's own jq output asserts the two
descriptions are identical, so the repos cannot drift quietly.
* Pre-images are cached in the state directory. The tier edits files the
operator owns, and `/etc/runicgateway/patches/originals/` is what turns "here
are the hunks we added" into a revert anyone can verify — kept out of the
ServUO tree, which uninstall has promised never to clean up.
Everything else follows §2.2.1: exact matching with only line-ending and
trailing-whitespace normalization, exactly one occurrence or it fails,
all-or-nothing per patch file and again per feature, and a byte-preserving
splice so nothing outside a hunk can be reformatted.
Verified against the ServUO 57.4 tree on this machine across four scratch
roots: a hand-patched tree (rung 0), a reverse-applied stock one (rung 1 on the
real EventSink.cs, its blob matching the patch's declared pre-image), a
mixed-rung feature, a tree with edits inside two patched regions (rung 3 —
nothing written, nothing held back applied, no companions copied), and a
non-57.4 tree both with and without the extra consent flag. Three consecutive
runs left install.json byte-identical and the cached pre-image still pre-patch.
Three reporting defects the live runs caught are fixed with tests: a dry run
and a held-back patch both claimed to be "applied", the core-rebuild warning
fired when nothing had been written and named a Scripts file as core, and a
declined tier announced the loss of features install.json showed as applied.
Refused patches are now cached too, since the refusal message names that path.
Refs: docs/installer/PLAN.md §2.2, §5 Phase 3
Co-Authored-By: Claude <noreply@anthropic.com>