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
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 2m7s
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
This commit is contained in:
@@ -68,7 +68,7 @@ pub struct Row {
|
||||
}
|
||||
|
||||
impl Row {
|
||||
fn new(mark: Mark, label: &str, detail: impl Into<String>) -> Self {
|
||||
pub(crate) fn new(mark: Mark, label: &str, detail: impl Into<String>) -> Self {
|
||||
Self {
|
||||
mark,
|
||||
label: label.to_string(),
|
||||
@@ -77,24 +77,24 @@ impl Row {
|
||||
}
|
||||
}
|
||||
|
||||
fn ok(label: &str, detail: impl Into<String>) -> Self {
|
||||
pub(crate) fn ok(label: &str, detail: impl Into<String>) -> Self {
|
||||
Self::new(Mark::Ok, label, detail)
|
||||
}
|
||||
|
||||
fn warn(label: &str, detail: impl Into<String>) -> Self {
|
||||
pub(crate) fn warn(label: &str, detail: impl Into<String>) -> Self {
|
||||
Self::new(Mark::Warn, label, detail)
|
||||
}
|
||||
|
||||
fn fail(label: &str, detail: impl Into<String>) -> Self {
|
||||
pub(crate) fn fail(label: &str, detail: impl Into<String>) -> Self {
|
||||
Self::new(Mark::Fail, label, detail)
|
||||
}
|
||||
|
||||
fn note(mut self, note: impl Into<String>) -> Self {
|
||||
pub(crate) fn note(mut self, note: impl Into<String>) -> Self {
|
||||
self.notes.push(note.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn notes_from(mut self, notes: impl IntoIterator<Item = String>) -> Self {
|
||||
pub(crate) fn notes_from(mut self, notes: impl IntoIterator<Item = String>) -> Self {
|
||||
self.notes.extend(notes);
|
||||
self
|
||||
}
|
||||
@@ -166,8 +166,14 @@ pub fn run(cli: &Cli) -> Result<i32> {
|
||||
rows.push(backup_row(&layout));
|
||||
|
||||
// ── Report ───────────────────────────────────────────────────────────────
|
||||
Ok(report(&rows))
|
||||
}
|
||||
|
||||
/// Prints the rows and the one-line verdict, and returns the exit code: `1` when anything failed.
|
||||
/// Shared with `doctor --game rust`, so both games' reports read the same way.
|
||||
pub(crate) fn report(rows: &[Row]) -> i32 {
|
||||
println!();
|
||||
for row in &rows {
|
||||
for row in rows {
|
||||
println!("{} {:<24} {}", row.mark.glyph(), row.label, row.detail);
|
||||
for note in &row.notes {
|
||||
println!(" {note}");
|
||||
@@ -185,7 +191,11 @@ pub fn run(cli: &Cli) -> Result<i32> {
|
||||
INSTALL.md's Troubleshooting table is keyed to these symptoms."
|
||||
),
|
||||
}
|
||||
Ok(if failed > 0 { 1 } else { 0 })
|
||||
if failed > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// The ServUO root to inspect: `--servuo` if given, else the one the record names.
|
||||
@@ -548,7 +558,7 @@ fn live_config(link: &LinkRecord) -> Option<sidecar::ConfigDoc> {
|
||||
///
|
||||
/// Always over loopback, never over the configured bind: `[web] bind` is regularly `0.0.0.0`, and
|
||||
/// this check is about whether the process on *this* host is answering.
|
||||
fn health_of(bind: &str) -> Option<Health> {
|
||||
pub(crate) fn health_of(bind: &str) -> Option<Health> {
|
||||
let port = bind.rsplit_once(':').map(|(_, p)| p).unwrap_or(bind);
|
||||
let url = format!("http://127.0.0.1:{port}/health");
|
||||
let body = net::get_text_within(&url, HEALTH_TIMEOUT).ok()?;
|
||||
|
||||
Reference in New Issue
Block a user