feat(rust): cut edge over to main — --game rust, named instances, schema-2 bundles (rust phase 18) #32

Merged
whitlocktech merged 9 commits from edge into main 2026-09-26 09:15:45 +00:00
Member

The installer's phase 18 cutover (§34.2.7): edge → main, which releases the installer that speaks --game rust (a feat:, so v0.3.0). It merges cleanly: main's three commits that edge lacks are #27 (the schema-2 compose, which went straight to main) and the Asset Bridge cutover merge.

What reaches main

  • #28: --game rust: named instances (--server-id, one service/config/db/port pair each), Oxide/Carbon detection, schema-2 bundles (ServUO lowered to schema 1, with v1 fallback), and doctor/update/uninstall for Rust.
  • #29 (D156): the handoff says "is set up", not "is connected"; --help fixes.
  • #30: an update on a running server now reloads the plugin. Before, it unloaded the plugin and never loaded the new one. Also truthful "nothing moved" and account-removal lines.
  • #31 (D157): Windows: the service starts after its account is granted its config. Before, the first start died on "Access is denied" and stayed STOPPED. This also fixes a first-time ServUO install on Windows, which has the same bug in v0.2.0.

Walked (§34.3), before this merge

On bundle 2026.09.26.x, built from real Rust-Link and Rust-Plugins releases:

  • Step 1: pipeline ✅
  • Step 2: Linux, Oxide, two instances ✅
  • Step 3: Carbon ✅
  • Step 4: Windows SCM ✅ (after #31)
  • Step 5: day two ✅ (after #30)

Step 6 (the egg) is in progress. Step 7 (step 2's first instance from the downloaded binary) runs on the release this merge produces.

ServUO field installs keep reading schema-1 current.json (D147), which is unchanged.

  • AI-assisted: Claude Code (Claude Opus 5.5)

🤖 Generated with Claude Code

https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY

The installer's phase 18 cutover (§34.2.7): `edge` → `main`, which releases the installer that speaks `--game rust` (a `feat:`, so **v0.3.0**). It merges cleanly: `main`'s three commits that `edge` lacks are #27 (the schema-2 compose, which went straight to `main`) and the Asset Bridge cutover merge. ## What reaches `main` - **#28**: `--game rust`: named instances (`--server-id`, one service/config/db/port pair each), Oxide/Carbon detection, schema-2 bundles (ServUO lowered to schema 1, with v1 fallback), and `doctor`/`update`/`uninstall` for Rust. - **#29** (D156): the handoff says "is set up", not "is connected"; `--help` fixes. - **#30**: an `update` on a **running** server now reloads the plugin. Before, it unloaded the plugin and never loaded the new one. Also truthful "nothing moved" and account-removal lines. - **#31** (D157): **Windows**: the service starts *after* its account is granted its config. Before, the first start died on "Access is denied" and stayed STOPPED. **This also fixes a first-time ServUO install on Windows**, which has the same bug in v0.2.0. ## Walked (§34.3), before this merge On bundle `2026.09.26.x`, built from real Rust-Link and Rust-Plugins releases: - Step 1: pipeline ✅ - Step 2: Linux, Oxide, two instances ✅ - Step 3: Carbon ✅ - Step 4: Windows SCM ✅ (after #31) - Step 5: day two ✅ (after #30) Step 6 (the egg) is in progress. **Step 7** (step 2's first instance from the *downloaded* binary) runs on the release this merge produces. ServUO field installs keep reading schema-1 `current.json` (D147), which is unchanged. - [x] AI-assisted: Claude Code (Claude Opus 5.5) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
wtclaude added 9 commits 2026-09-26 09:12:26 +00:00
feat(rust): --game rust, named instances, and schema-2 bundles (phase 18)
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 2m7s
7027a78a23
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
Reviewed-on: #28
fix(rust): the handoff says what the run did, not that a link exists (D156)
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 58s
3b2881eb9c
`install --game rust` ended every instance with "Rust server "alpha" is
connected to its sidecar" — printed unconditionally, before the plugin had
dialled anything, on a stopped server where it had not even loaded, and
beside "No service was registered, so nothing is listening yet". The
phase 18 walk read it as a claim and then found beta's plugin connected to
the wrong sidecar.

It now reads "Rust server "alpha" is set up." followed by either "The
plugin loads now; `doctor --game rust --server-id alpha` confirms it
connected." or "The plugin connects when the server next starts.".

The top-level --help no longer describes `install` as the uo-link sidecar
and overlay for both games, and says what `uninstall` removes for Rust.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Reviewed-on: #29
fix(rust): an update on a running server reloads the plugin
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 52s
397e0b92e9
The plugin was placed with write_atomic: remove the old file, rename a
.tmp over it. Oxide and Carbon watch the plugins directory and reload on
a change; both saw the remove as a delete and UNLOADED the bridge, and
ignored the rename, so the new file was never loaded. Every `update` (or
re-install) on a running server took the bridge down until the next
boot, silently — the phase 18 walk found it on all three instances and
both frameworks, after the move to v0.1.1.

The plugin is now overwritten in place, which is what an operator's `cp`
does and what both frameworks reload on. write_atomic stays for the
record and the configs, where atomicity is the point.

`update` on the same bundle also no longer says "nothing moved" after it
has just put back a hand-edited plugin — the remedy `doctor` names.

Walked: hand-edit, then `update`, on a running Oxide and a running
Carbon server: replaced, reloaded, reconnected, doctor clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
fix(service): say why the service account stays, truthfully for Rust too
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 54s
e5611a01eb
`uninstall --game rust --server-id beta` printed "left the runicgateway
account alone — this installer did not create it" on a host where this
installer had created it, for alpha. A Rust instance's service never owns
the shared account (the last instance's removal decides), so the reason
now reads "it is not this service's to remove" — true for both games.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Reviewed-on: #30
fix(service): start the Windows service after granting it its config (D157)
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m8s
70eb073826
`sc start` ran inside registration, before `grant_service_access` let the
new virtual account read `sidecar.toml` — which `protect_config` had
locked to SYSTEM and Administrators. The first start died on "Access is
denied (os error 5)" and the service sat STOPPED: the SCM's
restart-on-failure policy never fires for a clean exit with an error
code. A later manual start worked, because by then the grant existed.

Found on the phase 18 walk (step 4, one Rust instance under the SCM),
from the sidecar's own log. The code is shared, so the released ServUO
installer has the same first-start failure; the org lead chose to fix
both here (D157).

Registration no longer starts the service; a new `start_registered`
runs after the grant and refreshes the reported state. A no-op on Linux,
where the service user exists before the config and registration starts
the unit itself.

Walked (Windows, elevated): install -> START_PENDING/running, /health on
the first probe, clean stop and restart, doctor clean, uninstall clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Reviewed-on: #31
whitlocktech approved these changes 2026-09-26 09:15:38 +00:00
whitlocktech merged commit 0710a3e33e into main 2026-09-26 09:15:45 +00:00
whitlocktech deleted branch edge 2026-09-26 09:15:46 +00:00
Sign in to join this conversation.
No description provided.