feat(docs): phase 15 - the Rust group, its reference pages checked against their sources
All checks were successful
PR checks / checks (pull_request) Successful in 2m5s
All checks were successful
PR checks / checks (pull_request) Successful in 2m5s
PLAN.md §16 phase 15. A Rust group of fifteen pages after Ultima Online (D70): the module, installing the bridge (installer, Pterodactyl egg, by hand), connecting the site, verifying the bridge, linking Steam accounts, permissions and groups, mod config, who sees who is online, events, zones and rewards, optional plugins, the live map, troubleshooting, and three reference pages - rust-link's sidecar.toml, the plugin's RunicGateway.json, and the event kinds. checkReference.mjs gains three Rust sections rather than a new check: sidecar.toml keys and RUSTLINK_* overrides from Rust-Link config.rs, RunicGateway.json from the plugin's PluginConfig, and the event kinds in three classes against Module-Rust server/catalogue.js (D71). Every admin screen was walked on the walk site before it was described (D36). Core pages now hand off to the Rust group instead of linking out to the docs repository. Three defects the re-read found here are fixed: the UO /health sample's hard-coded protocol 4 (both games' samples now render platform.json through <Code>), Authentication's missing Steam tab, and two pages saying the site cannot reach EventsEnabled - false on Rust. Installer v0.8.1 moved checkFacts red mid-phase; platform.json follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
77
PLAN.md
77
PLAN.md
@@ -237,7 +237,7 @@ Taken by the org lead (Colby Whitlock) on 2026-08-19. Recorded so they are not r
|
||||
|
||||
**Decisions after D13 are recorded where they were taken**, in the section describing the phase that
|
||||
raised them, rather than appended here — a decision is only re-litigated when its reasoning is
|
||||
somewhere other than the thing it decided. The count of record is **sixty-nine**:
|
||||
somewhere other than the thing it decided. The count of record is **seventy-one**:
|
||||
|
||||
| # | Where | What it settled |
|
||||
|---|---|---|
|
||||
@@ -258,6 +258,7 @@ somewhere other than the thing it decided. The count of record is **sixty-nine**
|
||||
| D66 | §16, "How phase 13 built it" | The hero and footer show the Module API only; a protocol and a bundle belong to a game |
|
||||
| D67 | §16, "One rule the phases need" | A screenshot belongs to its game: a capture tagged with a game may carry its words, beside the game's name |
|
||||
| D68–D69 | §16, "How phase 14 built it" | A game's real example on a core page is a tagged, labelled block; *Verify the whole stack* stays in Getting started, game-neutral (amending D60) |
|
||||
| D70–D71 | §16, "How phase 15 built it" | The Rust group is fifteen pages, one per admin screen where it can be; the Rust event kinds are checked against the module's own classification |
|
||||
|
||||
---
|
||||
|
||||
@@ -2094,6 +2095,80 @@ repositories):
|
||||
- **The installer's `--help` says "Which game's shard side"** for `--game`, and `--site-url` names
|
||||
"Admin → Shard (uo-link)" for both games.
|
||||
|
||||
### How phase 15 built it
|
||||
|
||||
Two decisions, taken by the org lead on 2026-10-22 before any page was written. The count of
|
||||
record is now **seventy-one**.
|
||||
|
||||
**D70 — the Rust group is fifteen pages.** D60 named the topics and not the pages. The group
|
||||
follows the Ultima Online group's shape: an overview, then the journey (*Install the bridge*,
|
||||
*Connect the site*, *Verify the bridge*), then one page per admin screen where there is one
|
||||
(*Permissions and groups*, *Mod config from the site*, *Who sees who is online*, *Events, zones
|
||||
and rewards*), then *Linking Steam accounts*, *Optional plugins*, *The live map* and
|
||||
*Troubleshooting*, and three reference pages (*sidecar.toml*, *RunicGateway.json*, *Event
|
||||
kinds*). *Who sees who is online* and *Optional plugins* got pages of their own rather than
|
||||
being folded into the overview and the events page, because each has a screen or a trap of its
|
||||
own — RaidableBases must be restarted, never reloaded. The alternative was ten longer pages. The
|
||||
question put to the org lead called this option "14 pages" while listing fifteen; the list is
|
||||
what was chosen. RunicNPC's subgroup is phase 16's.
|
||||
|
||||
**D71 — the Rust event kinds are checked against the module's classification.** The plan said the
|
||||
protocol-14 event list would be checked "against its sources". There were two candidates:
|
||||
`PROTOCOL.md`, whose kinds are spread across tables from §8.4 to §20.12, and `Module-Rust`
|
||||
`server/catalogue.js`, which holds three lists in code — public, staff, and the presence subset
|
||||
of public. The second was chosen, for D39's reason: the visibility ladder is checked against the
|
||||
module that enforces it, and so is this. The module's own test already holds its lists to
|
||||
`PROTOCOL.md`, so the chain reaches the spec without this repo parsing prose tables. The page
|
||||
shows three classes, and each is compared separately, so a kind moving from staff to public
|
||||
upstream fails here twice.
|
||||
|
||||
**`checkReference.mjs` gained three Rust sections**, as the plan said, rather than a new check:
|
||||
rust-link's `sidecar.toml` (parsed from its serde structs by the same function as uo-link's, now
|
||||
shared) and the six `RUSTLINK_…` variables that override it; the bridge plugin's
|
||||
`RunicGateway.json`, read from the public fields of `PluginConfig` in `RunicGateway.cs`; and the
|
||||
event kinds (D71). The canonical documents gained `OPERATING.md` and `CARBON.md` under Rust.
|
||||
|
||||
**Every admin screen was walked on the walk site first** (D36), signed in as the walk admin:
|
||||
Rust servers (Test, Edit, Chat titles, Optional mods), Rust permissions (a player, a group's four
|
||||
tabs), Rust mod config (the tree and `RunicGateway.json`'s read-only keys), Rust visibility, Rust
|
||||
zone presets, the event editor's action list, a user's Rust section, and the public server page's
|
||||
feed, map and leaderboard. Nothing was saved. The NPC screens are phase 16's. Writing from the
|
||||
walk and the source corrected three of my own drafts before they shipped: a reward does **not**
|
||||
need a linked account (rewards go to Steam ids), the bundle ships **three** helpers, not two, and
|
||||
the Test button prints the raw status (`protocol-mismatch`), not a phrase.
|
||||
|
||||
**What the re-read found in this repository**, fixed here:
|
||||
|
||||
- ***Verify the bridge* (Ultima Online) showed `"protocol":4`** in its `/health` sample, a
|
||||
hard-coded version four protocols stale. Both games' samples now render the protocol from
|
||||
`platform.json` through Starlight's `<Code>`, which is how a version gets into a code block
|
||||
without being typed.
|
||||
- ***Authentication* said "four tabs"** and never mentioned Steam, which core shipped with Module
|
||||
API 1.17.0. It now has five, and a *Steam* section the Rust pages link to.
|
||||
- ***Scheduled events* and *Events architecture* said the site cannot reach `EventsEnabled`.**
|
||||
True for Ultima Online, false for Rust: an administrator can edit `RunicGateway.json`, that switch
|
||||
included, from Admin → Rust mod config. Both pages now say so, the first as two tagged examples
|
||||
(D68).
|
||||
|
||||
**`checkFacts` went red mid-phase, as designed.** Installer v0.8.1 shipped on 2026-10-22 — the
|
||||
game-neutral `--help` that phase 14 reported, fixed in `installer` #43 alongside `website` #221 for
|
||||
the Teams panel — so `platform.json` moved to it. Its only change is that help text, which no page
|
||||
quotes.
|
||||
|
||||
**Findings in other repositories**, reported rather than fixed:
|
||||
|
||||
- **`Module-Rust` admin copy shows decision numbers.** A group's *Servers* tab reads "unless it is
|
||||
shared (D189)", and the *not imported yet* chip's tooltip ends "(D198)". Neither means anything
|
||||
to an operator.
|
||||
- **`Module-Rust`'s servers page reads "Last pushed: 1 player hold one."** — the plural verb is
|
||||
used for one.
|
||||
- **`Rust-Plugins`' `PluginConfig.EventsEnabled` comment says it "gates taking a lease only"**.
|
||||
That stopped being true at protocol 9, when it began gating every world write.
|
||||
- **`docs` `rust-link/INTEGRATION.md` predates the installer**: it says there is no installer
|
||||
support for Rust and shows protocol-1 samples. **`rust-link/INSTALL.md`'s by-hand step names two
|
||||
helpers**; the bundle ships three. The org lead approved a separate `docs` PR for these:
|
||||
`docs` #370.
|
||||
|
||||
---
|
||||
|
||||
[api]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
* this is the machinery that makes writing the NAMES down safe anyway — the same bargain
|
||||
* checkQuickstart.mjs struck for the quickstart, applied to six more sources.
|
||||
*
|
||||
* Phase 15 (PLAN.md §16) added the Rust group's three: rust-link's `sidecar.toml` and its
|
||||
* environment overrides, the bridge plugin's `RunicGateway.json`, and the event kinds with the
|
||||
* class each is served under (D71).
|
||||
*
|
||||
* Each enumeration in `src/data/reference.mjs` is compared against its authority, read from
|
||||
* the repository that owns it over the Gitea API — never from a working tree, per §1's
|
||||
* process rule. Every comparison is a SET comparison in both directions:
|
||||
@@ -39,6 +43,10 @@ import {
|
||||
bridgeCfg,
|
||||
visibilityLadder,
|
||||
canonicalDocs,
|
||||
rustSidecarConfig,
|
||||
rustSidecarEnv,
|
||||
rustPluginConfig,
|
||||
rustEventKinds,
|
||||
} from '../src/data/reference.mjs';
|
||||
|
||||
const ROOT = fileURLToPath(new URL('..', import.meta.url));
|
||||
@@ -103,6 +111,45 @@ const cfgKeysOf = (text) =>
|
||||
.filter(Boolean)
|
||||
.map((m) => m[1]);
|
||||
|
||||
/**
|
||||
* A sidecar's TOML keys, parsed from its serde structs rather than from a sample file,
|
||||
* because the sample is GENERATED by the binary on first run and no committed copy is
|
||||
* authoritative. Each `pub name: T` inside a `struct XCfg` is one key, and the struct name
|
||||
* gives the section. Both sidecars are written this way, so one parser serves both.
|
||||
*/
|
||||
function tomlKeysOf(configRs) {
|
||||
const keys = [];
|
||||
for (const m of configRs.matchAll(/struct\s+(\w+)Cfg\s*\{([\s\S]*?)\n\}/g)) {
|
||||
const section = m[1].toLowerCase();
|
||||
for (const f of m[2].matchAll(/pub\s+(\w+)\s*:/g)) keys.push(`${section}.${f[1]}`);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/** The body of a C# class, by brace counting from its opening brace. */
|
||||
function classBody(source, name) {
|
||||
const start = source.search(new RegExp(`class\\s+${name}\\b`));
|
||||
if (start < 0) return null;
|
||||
const open = source.indexOf('{', start);
|
||||
let depth = 0;
|
||||
for (let i = open; i < source.length; i++) {
|
||||
if (source[i] === '{') depth++;
|
||||
else if (source[i] === '}' && --depth === 0) return source.slice(open + 1, i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The quoted strings inside `const NAME = Object.freeze([ … ])`, comments removed first —
|
||||
* the module's comments are prose, and prose has apostrophes.
|
||||
*/
|
||||
function frozenListOf(source, name) {
|
||||
const m = source.match(new RegExp(`const\\s+${name}\\s*=\\s*Object\\.freeze\\(\\[([\\s\\S]*?)\\]\\)`));
|
||||
if (!m) return null;
|
||||
const code = m[1].replace(/\/\/.*$/gm, '');
|
||||
return [...code.matchAll(/'([^'\n]+)'/g)].map((x) => x[1]);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (!TOKEN) {
|
||||
console.error('checkReference: GITEA_TOKEN is not set. This check cannot run anonymously.');
|
||||
@@ -122,13 +169,12 @@ async function run() {
|
||||
// Parsed from the serde structs rather than from a sample file, because the sample is
|
||||
// GENERATED by the binary on first run and no committed copy is authoritative. Each
|
||||
// `pub name: T` inside a `struct XCfg` is one key, and the struct name gives the section.
|
||||
const configRs = await raw('link', 'sidecar/src/config.rs');
|
||||
const sidecarKeys = [];
|
||||
for (const m of configRs.matchAll(/struct\s+(\w+)Cfg\s*\{([\s\S]*?)\n\}/g)) {
|
||||
const section = m[1].toLowerCase();
|
||||
for (const f of m[2].matchAll(/pub\s+(\w+)\s*:/g)) sidecarKeys.push(`${section}.${f[1]}`);
|
||||
}
|
||||
compareSets('sidecar.toml', Object.keys(sidecarConfig), sidecarKeys, 'link main:sidecar/src/config.rs');
|
||||
compareSets(
|
||||
'sidecar.toml',
|
||||
Object.keys(sidecarConfig),
|
||||
tomlKeysOf(await raw('link', 'sidecar/src/config.rs')),
|
||||
'link main:sidecar/src/config.rs',
|
||||
);
|
||||
|
||||
// ── 3. Installer commands ─────────────────────────────────────────────────
|
||||
const cliRs = await raw('installer', 'src/cli.rs');
|
||||
@@ -175,6 +221,72 @@ async function run() {
|
||||
else fail(`canonical doc ${docPath}`, `not found in docs main (HTTP ${res.status})`);
|
||||
}
|
||||
|
||||
// ── 7. Rust: rust-link's sidecar.toml, and the variables that override it ──
|
||||
//
|
||||
// PLAN.md §16, phase 15. The same parser as uo-link's — both sidecars declare their file
|
||||
// as serde structs — plus the environment overrides, which a Pterodactyl egg sets and an
|
||||
// operator meets on the panel before they ever see the file.
|
||||
const rustConfigRs = await raw('Rust-Link', 'sidecar/src/config.rs');
|
||||
compareSets(
|
||||
'rust-link sidecar.toml',
|
||||
Object.keys(rustSidecarConfig),
|
||||
tomlKeysOf(rustConfigRs),
|
||||
'Rust-Link main:sidecar/src/config.rs',
|
||||
);
|
||||
compareSets(
|
||||
'rust-link environment variable',
|
||||
Object.keys(rustSidecarEnv),
|
||||
[...rustConfigRs.matchAll(/get\("(RUSTLINK_[A-Z_]+)"\)/g)].map((m) => m[1]),
|
||||
'Rust-Link main:sidecar/src/config.rs',
|
||||
);
|
||||
for (const [envName, key] of Object.entries(rustSidecarEnv)) {
|
||||
if (!(key in rustSidecarConfig)) fail(`rust-link environment variable ${envName}`, `says it overrides "${key}", which is not a key listed here`);
|
||||
}
|
||||
|
||||
// ── 8. Rust: the bridge plugin's RunicGateway.json ────────────────────────
|
||||
//
|
||||
// The public fields of `PluginConfig`, which the framework serialises as the file.
|
||||
const pluginCs = await raw('Rust-Plugins', 'overlay/oxide/plugins/RunicGateway.cs');
|
||||
const pluginBody = classBody(pluginCs, 'PluginConfig');
|
||||
if (!pluginBody) {
|
||||
fail('RunicGateway.json', 'could not find class PluginConfig in Rust-Plugins main:overlay/oxide/plugins/RunicGateway.cs');
|
||||
} else {
|
||||
compareSets(
|
||||
'RunicGateway.json',
|
||||
Object.values(rustPluginConfig).flatMap((group) => Object.keys(group)),
|
||||
[...pluginBody.matchAll(/public\s+[\w<>[\],]+\s+(\w+)\s*=/g)].map((m) => m[1]),
|
||||
'Rust-Plugins main:overlay/oxide/plugins/RunicGateway.cs PluginConfig',
|
||||
);
|
||||
}
|
||||
|
||||
// ── 9. Rust: the event kinds, and who may see each ────────────────────────
|
||||
//
|
||||
// D71: checked against the module's own catalogue, because the module is what enforces
|
||||
// the boundary (the same reasoning as check 5). Three comparisons, each a security
|
||||
// statement: a kind listed public here must be public there, a presence kind must be one
|
||||
// of the module's presence kinds, and a staff kind must be staff there. A kind moved from
|
||||
// staff to public upstream fails this check twice over, which is the point.
|
||||
const catalogue = await raw('Module-Rust', 'server/catalogue.js');
|
||||
const upPublic = frozenListOf(catalogue, 'PUBLIC_KINDS');
|
||||
const upStaff = frozenListOf(catalogue, 'STAFF_KINDS');
|
||||
const upPresence = frozenListOf(catalogue, 'PRESENCE_KINDS');
|
||||
if (!upPublic || !upStaff || !upPresence) {
|
||||
fail('Rust event kinds', 'could not find PUBLIC_KINDS, STAFF_KINDS and PRESENCE_KINDS in Module-Rust main:server/catalogue.js');
|
||||
} else {
|
||||
const hint = 'Module-Rust main:server/catalogue.js';
|
||||
compareSets('Rust presence kind', Object.keys(rustEventKinds.presence), upPresence, `${hint} PRESENCE_KINDS`);
|
||||
compareSets(
|
||||
'Rust public kind',
|
||||
Object.keys(rustEventKinds.public),
|
||||
upPublic.filter((k) => !upPresence.includes(k)),
|
||||
`${hint} PUBLIC_KINDS (less its presence kinds)`,
|
||||
);
|
||||
compareSets('Rust staff kind', Object.keys(rustEventKinds.staff), upStaff, `${hint} STAFF_KINDS`);
|
||||
for (const k of upPresence) {
|
||||
if (!upPublic.includes(k)) fail(`Rust presence kind ${k}`, 'is a presence kind upstream but not a public one — the page explains presence as a narrowing of public, so re-read it');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Report ────────────────────────────────────────────────────────────────
|
||||
if (failures.length === 0) {
|
||||
console.log(`checkReference: ${checked.length} enumeration check(s) passed against their sources.`);
|
||||
|
||||
@@ -98,6 +98,26 @@ export const docsSidebar = [
|
||||
{ label: 'Shard event catalog', slug: 'docs/ultima-online/event-catalog' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Rust',
|
||||
items: [
|
||||
{ label: 'The Rust module', slug: 'docs/rust' },
|
||||
{ label: 'Install the bridge', slug: 'docs/rust/install-the-bridge' },
|
||||
{ label: 'Connect the site', slug: 'docs/rust/connect-the-site' },
|
||||
{ label: 'Verify the bridge', slug: 'docs/rust/verify-the-bridge' },
|
||||
{ label: 'Linking Steam accounts', slug: 'docs/rust/linking-steam-accounts' },
|
||||
{ label: 'Permissions and groups', slug: 'docs/rust/permissions-and-groups' },
|
||||
{ label: 'Mod config from the site', slug: 'docs/rust/mod-config' },
|
||||
{ label: 'Who sees who is online', slug: 'docs/rust/who-sees-who-is-online' },
|
||||
{ label: 'Events, zones and rewards', slug: 'docs/rust/events-zones-and-rewards' },
|
||||
{ label: 'Optional plugins', slug: 'docs/rust/optional-plugins' },
|
||||
{ label: 'The live map', slug: 'docs/rust/the-live-map' },
|
||||
{ label: 'Troubleshooting', slug: 'docs/rust/troubleshooting' },
|
||||
{ label: 'sidecar.toml', slug: 'docs/rust/sidecar-toml' },
|
||||
{ label: 'RunicGateway.json', slug: 'docs/rust/runicgateway-json' },
|
||||
{ label: 'Event kinds', slug: 'docs/rust/event-kinds' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -174,4 +194,22 @@ export const plannedSidebar = {
|
||||
'sidecar.toml',
|
||||
'Shard event catalog',
|
||||
],
|
||||
// PLAN.md §16, D70 (phase 15). RunicNPC's subgroup lands in phase 16 (D61).
|
||||
Rust: [
|
||||
'The Rust module',
|
||||
'Install the bridge',
|
||||
'Connect the site',
|
||||
'Verify the bridge',
|
||||
'Linking Steam accounts',
|
||||
'Permissions and groups',
|
||||
'Mod config from the site',
|
||||
'Who sees who is online',
|
||||
'Events, zones and rewards',
|
||||
'Optional plugins',
|
||||
'The live map',
|
||||
'Troubleshooting',
|
||||
'sidecar.toml',
|
||||
'RunicGateway.json',
|
||||
'Event kinds',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Local accounts and two-factor, SSO providers and the link-only poli
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
**Admin → Authentication** has four tabs: Local Accounts, Google, Discord and Custom
|
||||
**Admin → Authentication** has five tabs: Local Accounts, Google, Discord, Steam and Custom
|
||||
Providers. One session model sits behind all of them — a web cookie, a mobile bearer token
|
||||
and an SSO sign-in all produce the same session.
|
||||
|
||||
@@ -35,6 +35,19 @@ account is still a stranger.
|
||||
Configuring Google here also unlocks **email delivery**, which reuses the same OAuth client
|
||||
— see [Notifications and email](/docs/administration/notifications-and-email/).
|
||||
|
||||
### Steam
|
||||
|
||||
Steam signs people in with OpenID rather than OAuth, so it needs no client ID or secret — but
|
||||
it does need a **Steam Web API key**, for the name and avatar Steam's sign-in does not carry,
|
||||
and saving Steam as enabled without one is refused. It follows the same link-only policy, more
|
||||
strictly: **Steam never creates an account**, whatever registration allows. A person may link
|
||||
several Steam accounts, and each is listed on their account screen with its name, avatar and
|
||||
Steam ID.
|
||||
|
||||
A game module can also be told which Steam accounts a signed-in person has proved are theirs,
|
||||
which is how a game that knows its players by Steam account can recognise a member. The Rust
|
||||
module uses it — see [Linking Steam accounts](/docs/rust/linking-steam-accounts/).
|
||||
|
||||
## Trusted devices
|
||||
|
||||
A second factor that asks on every sign-in on the same laptop trains people to click
|
||||
|
||||
@@ -141,8 +141,8 @@ restart puts every borrowed value back too.
|
||||
|
||||
## The game server has its own switches
|
||||
|
||||
They live in the bridge plugin's config on the game server's host, outside the site's reach,
|
||||
and the site cannot turn them on.
|
||||
They live in the bridge plugin's config on the game server's host, not in the site's
|
||||
database, and no event can turn them on.
|
||||
|
||||
**`EventsEnabled` is off by default**, in both games. Turning it on is consenting to the site
|
||||
changing and watching your world unattended.
|
||||
@@ -151,7 +151,14 @@ changing and watching your world unattended.
|
||||
`EventsEnabled` is a different switch from `AdminWriteEnabled`. Turning the admin plane on is
|
||||
consenting to staff moderation driven from a screen somebody is looking at; turning events on
|
||||
is consenting to unattended changes. One switch could not honestly express both. Every key
|
||||
is in [`Bridge.cfg`](/docs/ultima-online/bridge-cfg/).
|
||||
is in [`Bridge.cfg`](/docs/ultima-online/bridge-cfg/), which only the shard host can edit.
|
||||
</GameExample>
|
||||
|
||||
<GameExample game="rust">
|
||||
`EventsEnabled` is in each server's [`RunicGateway.json`](/docs/rust/runicgateway-json/). An
|
||||
administrator can edit it on the host, or from the site's
|
||||
[Mod config](/docs/rust/mod-config/) screen, which reaches the game host's config files
|
||||
through the bridge.
|
||||
</GameExample>
|
||||
|
||||
Beside it sit the game's own ceilings — how much one call may place, how long something it
|
||||
|
||||
@@ -82,7 +82,7 @@ Two responses from a sidecar mean the same thing for every game: **`409`** is a
|
||||
mismatch (the sidecar refuses rather than mis-parsing), and **`401`** is the auth token.
|
||||
|
||||
Each game's group in the sidebar has its own troubleshooting page with the exact commands:
|
||||
[Ultima Online](/docs/ultima-online/troubleshooting/) today.
|
||||
[Ultima Online](/docs/ultima-online/troubleshooting/) and [Rust](/docs/rust/troubleshooting/).
|
||||
|
||||
## Teams are missing
|
||||
|
||||
|
||||
@@ -114,11 +114,13 @@ something must not take that path.
|
||||
are re-validated against the database on every request, so a demotion takes effect on the
|
||||
next click.
|
||||
|
||||
The game's switches are the layer the site cannot reach: they live in the bridge plugin's
|
||||
own config on the game server's host — `EventsEnabled`, in both games — and are off out of
|
||||
the box, and the
|
||||
game's own ceilings **refuse rather than clamp** — because a silently shortened request leaves
|
||||
the two halves disagreeing about what happened.
|
||||
The game's switches are the layer outside the site's own database: they live in the bridge
|
||||
plugin's own config on the game server's host — `EventsEnabled`, in both games — and are off
|
||||
out of the box. No event and no role check can turn one on. On Rust an administrator can
|
||||
still edit that file from the game module's config screen, which reaches the host's config
|
||||
files through the bridge, so there it is a separate deliberate act rather than an unreachable
|
||||
one. The game's own ceilings **refuse rather than clamp** — because a silently shortened
|
||||
request leaves the two halves disagreeing about what happened.
|
||||
|
||||
<Aside type="note" title="Stated plainly">
|
||||
The module boundary is **not** a security boundary — a module runs in the same process with
|
||||
|
||||
@@ -105,7 +105,7 @@ website about. Every catalog has the same structure, though: kinds the module de
|
||||
each one either safe for the public stream or kept to the admin one.
|
||||
|
||||
- **Ultima Online:** [its event catalog](/docs/ultima-online/event-catalog/), in the Ultima Online group
|
||||
- **Rust:** [the Rust bridge protocol](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md) in the `docs` repository
|
||||
- **Rust:** [its event kinds](/docs/rust/event-kinds/), in the Rust group
|
||||
|
||||
## Two design details worth stealing
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ protocol-checked pair of sidecar and bridge plugin — never "the latest of each
|
||||
| Game | Its steps |
|
||||
|---|---|
|
||||
| **{platform.games.uo.name}** | [Connecting the server](/docs/ultima-online/connect-a-servuo-shard/), then [verifying its bridge](/docs/ultima-online/verify-the-bridge/), in the Ultima Online group |
|
||||
| **{platform.games.rust.name}** | [The Rust bridge install guide](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INSTALL.md) in the `docs` repository: the installer, a Pterodactyl egg, or by hand |
|
||||
| **{platform.games.rust.name}** | [Installing the bridge](/docs/rust/install-the-bridge/) — the installer, a Pterodactyl egg, or by hand — then [connecting the site](/docs/rust/connect-the-site/), in the Rust group |
|
||||
|
||||
## If the website is on a different machine
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ There are two, both built inside this project, and a site runs **one** of them:
|
||||
| **{platform.games.rust.name}** | [releases](https://gitea.whitlocktech.com/RunicGateway/Module-Rust/releases) | {platform.releases['Module-Rust']} |
|
||||
|
||||
What each one adds is in its own group in the sidebar:
|
||||
[Ultima Online](/docs/ultima-online/) today.
|
||||
[Ultima Online](/docs/ultima-online/) and [Rust](/docs/rust/).
|
||||
|
||||
## Install it
|
||||
|
||||
|
||||
@@ -40,8 +40,7 @@ game:
|
||||
| **The sidecar on the same host as the game server** | The bridge plugin dials the sidecar over loopback. That loopback socket *is* the trust boundary for inbound commands to the game, so the two are never split across machines. |
|
||||
|
||||
Then the game's own list: [Ultima Online](/docs/ultima-online/connect-a-servuo-shard/#what-the-shard-host-needs),
|
||||
or the *Before you begin* section of
|
||||
[the Rust bridge install guide](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INSTALL.md#before-you-begin).
|
||||
or [Rust](/docs/rust/install-the-bridge/#before-you-begin).
|
||||
|
||||
## What you do not need
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ The order is the same for every game. The commands and the screen names are not,
|
||||
game has its own page with the exact steps:
|
||||
|
||||
- **Ultima Online:** [Verify the bridge](/docs/ultima-online/verify-the-bridge/)
|
||||
- **Rust:** the *Connect the website* and *Day two* sections of
|
||||
[the Rust bridge install guide](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INSTALL.md)
|
||||
- **Rust:** [Verify the bridge](/docs/rust/verify-the-bridge/)
|
||||
|
||||
## Two responses that mean the same for every game
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ authentication, Teams, scheduled events, moderation, notifications, modules, upg
|
||||
what to do when something is wrong.
|
||||
|
||||
Everything that belongs to one game has its own group at the end of the sidebar, after
|
||||
**Reference**: [Ultima Online](/docs/ultima-online/) today.
|
||||
**Reference**: [Ultima Online](/docs/ultima-online/) and [Rust](/docs/rust/).
|
||||
|
||||
## Where the truth lives
|
||||
|
||||
|
||||
@@ -45,7 +45,9 @@ runicgateway-installer install --game rust --rust /srv/rust --server-id main
|
||||
```
|
||||
|
||||
One sidecar serves one Rust server, so `--server-id` is required on install. The Rust flags
|
||||
are refused on a run that is not `--game rust`, rather than silently ignored.
|
||||
are refused on a run that is not `--game rust`, rather than silently ignored. The whole Rust
|
||||
install, including a Pterodactyl egg for hosts the installer cannot run on, is in
|
||||
[its group](/docs/rust/install-the-bridge/).
|
||||
</GameExample>
|
||||
|
||||
## Rehearsing a run
|
||||
|
||||
108
src/content/docs/docs/rust/connect-the-site.mdx
Normal file
108
src/content/docs/docs/rust/connect-the-site.mdx
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: Connect the site
|
||||
description: Adding a Rust server to the site on Admin → Rust servers, what the Test button's answers mean, and the rest of a server's settings — its wipe schedule, world-event credit and chat titles.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
Every Rust server the site follows is one row on **Admin → Rust servers**
|
||||
(`/admin/rust/servers`). A row tells the site where that server's sidecar is; the sidecar
|
||||
itself lives on the game host, put there by [Install the bridge](/docs/rust/install-the-bridge/).
|
||||
|
||||
## Add a server
|
||||
|
||||
Press **Add a server** and fill in four values:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Id** | The server id — the installer's `--server-id`, or the egg's variable. It must equal `ServerId` in the plugin's config |
|
||||
| **Name** | Anything. Players see it |
|
||||
| **Sidecar address** | The sidecar URL the installer or the console printed, such as `http://10.0.0.5:8090` |
|
||||
| **Sidecar token** | The token, from the sidecar's own config |
|
||||
|
||||
**The id is permanent.** It is in every link to the server and the site will not change it
|
||||
later. It is deliberately not the hostname: you will rename a server for a season, and its
|
||||
history must not go with the name. Changing the plugin's `ServerId` after the site has seen it
|
||||
strands everything recorded under the old one.
|
||||
|
||||
**The token is write-only.** It is stored encrypted and never shown again once saved; the row
|
||||
says only *token stored*. Saving the form with the field blank keeps the stored token, so
|
||||
renaming a server does not mean re-pasting a credential.
|
||||
|
||||
## Test it
|
||||
|
||||
**Test the sidecar** asks the sidecar for its health and reports what came back. A working
|
||||
server answers *The sidecar answered (protocol N)*, and its row reads online, with its
|
||||
player count, the protocol and *last seen just now*. *The sidecar answered, but the game's
|
||||
plugin is not connected to it* means the site's half works and the game's does not — go to
|
||||
[Verify the bridge](/docs/rust/verify-the-bridge/).
|
||||
|
||||
A wrong address, a wrong token and a mismatched protocol all look the same from the public
|
||||
site — *the server is offline* — so the Test button is what tells them apart. A failure reads
|
||||
*The sidecar did not answer:* followed by one of these:
|
||||
|
||||
| Status | What is wrong | Where to look |
|
||||
|---|---|---|
|
||||
| `no-token` | The form was saved without one | This screen |
|
||||
| `unauthorized` | The token does not match | The sidecar's config on the game host |
|
||||
| `protocol-mismatch` | The sidecar and the module speak different protocols | Update whichever is behind |
|
||||
| `timeout` | The sidecar answered too slowly, or not at all | The sidecar's own log |
|
||||
| `transport-error` | Nothing is listening at that address | The address, the firewall, whether the sidecar runs |
|
||||
| `http-<code>` | Something answered, and it was not a sidecar | Usually a reverse proxy in front of the wrong thing |
|
||||
|
||||
Within a poll interval the server appears at `/rust`.
|
||||
|
||||
## If the site is on another machine
|
||||
|
||||
The installer's sidecar listens on `127.0.0.1`, reachable only from the game host. Put a TLS
|
||||
reverse proxy or a VPN link in front of it and give the site that URL — the token is a plain
|
||||
bearer token, and the sidecar speaks HTTP. The egg's sidecar listens on its allocation, which
|
||||
the network can reach, so firewall it to your site's address.
|
||||
|
||||
<Aside type="caution" title="Leave the game bind on loopback">
|
||||
The sidecar's other socket, the one the bridge plugin dials, takes commands for the game
|
||||
server and has no token. Being loopback-only is what makes that safe. Only the web bind ever
|
||||
moves.
|
||||
</Aside>
|
||||
|
||||
## The rest of a server's settings
|
||||
|
||||
**Edit** on a row opens the same form, plus:
|
||||
|
||||
- **Enabled** and **Order** — whether the site follows the server, and where it sits in the
|
||||
list.
|
||||
- **Wipe schedule.** *No schedule*, *Forced wipe only* (Facepunch's monthly forced wipe, the
|
||||
first Thursday of the month at 19:00 UK time), *Weekly* or *Every other week*, on a day,
|
||||
time and time zone you choose. A **one-off wipe** covers a delayed or extra one. It is a
|
||||
forecast: the next wipe shows on the server's public page. The wipe itself is whatever the
|
||||
game reports, which is what splits the leaderboard and the history.
|
||||
- **Who downs a helicopter or a Bradley.** *Killing blow* (the default), *Last hit*, *Most
|
||||
damage*, or *Everyone who hurt it*. It decides whose name the feed shows and who it counts
|
||||
for, from the moment you save.
|
||||
|
||||
**Optional mods** on a row lists what the bridge found on that server — BetterChat,
|
||||
PopupNotifications, Economics, Backpacks, TruePVE, Dynamic PVP, Dangerous Treasures, Guarded
|
||||
Crate, ZLevels, RaidableBases — with each one's version and what it lets the site do there.
|
||||
[Optional plugins](/docs/rust/optional-plugins/) explains each.
|
||||
|
||||
### Chat titles
|
||||
|
||||
**Chat titles** on a row gives titles to the top players of the current wipe, one rule per
|
||||
stat: *the top 1 by guarded crates cleared earns "Warden"*. Up to ten rules a server; a stat
|
||||
of zero earns nothing, so a fresh wipe has no titles until somebody plays. Titles show beside
|
||||
the name on the leaderboard, and in game chat where BetterChat is installed.
|
||||
|
||||
Below the servers, **Category titles** sets each stat's default title once for every server,
|
||||
and **Announcement voice** lets news posts and event announcements said in game chat wear the
|
||||
title and colours of one permission group that has a chat style. That style is set on the
|
||||
group, in [Permissions and groups](/docs/rust/permissions-and-groups/#chat-styles).
|
||||
|
||||
Next: [Verify the bridge](/docs/rust/verify-the-bridge/).
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/INSTALL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INSTALL.md#connect-the-website)
|
||||
and [`rust-link/INTEGRATION.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INTEGRATION.md)
|
||||
§3 hold the connection and the Test button's answers;
|
||||
[`modules/rust/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust/PLAN.md)
|
||||
is the module's design of record.
|
||||
106
src/content/docs/docs/rust/event-kinds.mdx
Normal file
106
src/content/docs/docs/rust/event-kinds.mdx
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: Event kinds
|
||||
description: Every kind of event a Rust server can tell the site, in three classes — public, presence and staff — and why the module, not the game, decides which is which.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import platform from '../../../../data/platform.json';
|
||||
import { rustEventKinds } from '../../../../data/reference.mjs';
|
||||
|
||||
What a Rust server tells the site, unprompted, as things happen in the world — every kind the
|
||||
bridge sends through protocol {platform.games.rust.protocol}, and who may see each.
|
||||
|
||||
**Not to be confused with a scheduled event.** That is a thing you put on the calendar and run —
|
||||
see [Events, zones and rewards](/docs/rust/events-zones-and-rewards/). The two meet: what happens
|
||||
here is what a phase can wait for.
|
||||
|
||||
The exact wire shapes are in the protocol specification and are **not** restated here. This page
|
||||
is the map and the security model.
|
||||
|
||||
## The module decides, not the game
|
||||
|
||||
The wire carries no visibility field. **A boundary is enforced by the side that serves, never
|
||||
declared by the side that sends**: a field the game stamped on each frame would be a boundary that
|
||||
a compromised — or merely out-of-date — game host could widen. So the Rust module holds the lists
|
||||
below, and they are **default-deny**: a kind it has never heard of, from a newer bridge, is not
|
||||
public until somebody classifies it.
|
||||
|
||||
These three lists are checked against the module's own on every build. A kind moved between
|
||||
classes upstream turns this page red.
|
||||
|
||||
## Public
|
||||
|
||||
Anybody may see these. Some are filtered field by field for each viewer — a world event's player
|
||||
name only for the presence audience, a Steam id and a position only for staff — but the kind
|
||||
itself is public.
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Kind</th><th>What it says</th></tr></thead>
|
||||
<tbody>
|
||||
{Object.entries(rustEventKinds.public).map(([kind, what]) => (
|
||||
<tr key={kind}><td><code>{kind}</code></td><td>{what}</td></tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Presence
|
||||
|
||||
Public kinds that say a **named** player was on the server at a given moment. Their audience is an
|
||||
operator's choice on [Who sees who is online](/docs/rust/who-sees-who-is-online/), and it is **staff
|
||||
only** until somebody widens it.
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Kind</th><th>What it says</th></tr></thead>
|
||||
<tbody>
|
||||
{Object.entries(rustEventKinds.presence).map(([kind, what]) => (
|
||||
<tr key={kind}><td><code>{kind}</code></td><td>{what}</td></tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
`player.death` and `player.chat` are here on purpose. They are the kill feed and the chat — the
|
||||
content a feed exists for — and each says *this person was on at 12:03* as plainly as a connect
|
||||
does.
|
||||
|
||||
<Aside type="note" title="Gathering is counted, not sent">
|
||||
A tree is hit hundreds of times a minute. One frame per swing would make the bridge the most
|
||||
expensive thing on the server, so the plugin keeps a per-player tally and sends `player.tally`
|
||||
once a minute, on disconnect and at shutdown. The leaderboard is exact and the wire is quiet. The
|
||||
rule behind it: if a hook can fire more than once a second per player, it is a counter, not an
|
||||
event.
|
||||
</Aside>
|
||||
|
||||
## Staff
|
||||
|
||||
Admins and moderators, and nobody else, at any setting.
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Kind</th><th>What it says</th></tr></thead>
|
||||
<tbody>
|
||||
{Object.entries(rustEventKinds.staff).map(([kind, what]) => (
|
||||
<tr key={kind}><td><code>{kind}</code></td><td>{what}</td></tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Bans, unbans, login attempts and approvals carry **IP addresses**, and `player.reported` carries the text of one player's
|
||||
complaint about another. They are stored because an operator chasing ban evasion needs them; they
|
||||
reach no page below admin, and the sidecar's raw history that holds them is bounded by
|
||||
`retain_days` in [`sidecar.toml`](/docs/rust/sidecar-toml/).
|
||||
|
||||
Clan membership changes are staff kinds on the server's feed, but not hidden from the clan: they
|
||||
reach a clan's own members through its [Team](/docs/administration/teams/) feed, where core decides
|
||||
who is a member.
|
||||
|
||||
## Every frame says when and where
|
||||
|
||||
Every frame carries the server's id and the **wipe** it happened in — derived by the plugin from
|
||||
the save's creation time, absent on a server that has never saved. A wipe splits a server's history
|
||||
rather than ending it, which is how the leaderboard is per wipe while a player's totals survive.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§8 defines the catalogue and every later section adds to it;
|
||||
[`server/catalogue.js`](https://gitea.whitlocktech.com/RunicGateway/Module-Rust/src/branch/main/server/catalogue.js)
|
||||
in the module is the classification this page is checked against.
|
||||
104
src/content/docs/docs/rust/events-zones-and-rewards.mdx
Normal file
104
src/content/docs/docs/rust/events-zones-and-rewards.mdx
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: Events, zones and rewards
|
||||
description: What the Rust module adds to core's scheduled events — its steps, the values an event may borrow, zone presets, and the switch on each server that has to be on first.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
An event is core's: phases, steps, a schedule, a dry run, and a record of what happened — see
|
||||
[Scheduled events](/docs/administration/events/). This page is what the Rust module puts into
|
||||
that editor, and what a Rust server needs before any of it can run.
|
||||
|
||||
## First, switch it on
|
||||
|
||||
Each server's bridge plugin has **`EventsEnabled`**, and it is **off** until you turn it on —
|
||||
in [`RunicGateway.json`](/docs/rust/runicgateway-json/), from
|
||||
[Mod config](/docs/rust/mod-config/) or on the host. It gates everything an event writes: a
|
||||
borrowed value, a zone, a crate, an NPC, a tally, a line in chat.
|
||||
|
||||
Letting staff moderate from a screen they are looking at and letting a schedule change the world
|
||||
at four in the morning are different consents, so this is its own switch. Turning it off never
|
||||
strands anything: giving back what an event took, and cleaning up what it placed, always work.
|
||||
|
||||
<Aside type="note" title="A server's own ceilings">
|
||||
The same file holds how much one step may do on that server — crates, NPCs, people rewarded,
|
||||
tallies at once, the length of a chat line, world events called. **A step over a bound is
|
||||
refused, never trimmed to fit.** An operator may lower each bound; none can be raised past the
|
||||
plugin's own ceiling.
|
||||
</Aside>
|
||||
|
||||
## The steps
|
||||
|
||||
Picked from the step's **Action** list, beside core's own:
|
||||
|
||||
| Group | Step | What it does |
|
||||
|---|---|---|
|
||||
| **The world** | Open a zone | A ZoneManager zone, at a monument or coordinates, with flags from a [preset](#zone-presets), messages on entering and leaving, and optionally a dome |
|
||||
| | Place crates | Crates, barrels and supply drops |
|
||||
| | Place NPCs | Rust's scientists and guards, or RunicNPC profiles — placed through RunicNPC |
|
||||
| | Call a world event | Rust's own: a patrol helicopter, an airdrop, the cargo ship, a Bradley, the Travelling Vendor and the rest |
|
||||
| **Counting** | Start counting participants | A tally of who is present in a zone or on the server, by seconds present, kills, or both |
|
||||
| | Record participants | Reads the tally into the run, so later steps can reward from it |
|
||||
| **Rewards** | Reward a kit | A Kits kit, as a credit the player redeems |
|
||||
| | Pay coins | Economics coins |
|
||||
| | Bigger backpack | Lends a larger Backpacks size until the event ends |
|
||||
| | Permissions until the event ends | Lent, and taken back at the end |
|
||||
| | Permissions for good | Given, and kept. Needs a tick saying you are sure |
|
||||
| | Reward by console command | Commands run once per recipient, with `{steamid}` and `{name}` filled in |
|
||||
| **Saying it** | Say it in game chat | A line in chat, or a popup where PopupNotifications is installed |
|
||||
| **Optional plugins** | Start a Dangerous Treasures event | |
|
||||
| | Place a guarded crate | |
|
||||
| | Spawn a raid base | A RaidableBases base that is the event's own |
|
||||
| **Anything else** | Run console commands | Free text, as typed at the server console, with commands to run when the event ends. Needs a tick saying you are sure |
|
||||
|
||||
The optional-plugin steps need their plugin on the server; [Optional
|
||||
plugins](/docs/rust/optional-plugins/) covers each. Every reward step picks its recipients the same
|
||||
way: everyone who scored, the top N, a minimum score, N drawn at random, the top X per cent — or
|
||||
every player with a [linked Steam account](/docs/rust/linking-steam-accounts/), with no tally at
|
||||
all, for something like a kit weekend. A recipient need not be linked otherwise: rewards go to
|
||||
Steam accounts, and one who is offline gets theirs on their next time in.
|
||||
|
||||
**What an event places is the event's.** At its end, the run gives back what it borrowed and
|
||||
removes what it placed — its zones, crates, NPCs, and a raid base it spawned. A server restart in
|
||||
the middle does not lose track: the plugin keeps the registry on disk, and something that outlives
|
||||
its deadline is removed and recorded as *expired*.
|
||||
|
||||
## Values an event may borrow
|
||||
|
||||
Core's **Borrow a value** step holds a setting for the length of the event and gives it back at
|
||||
the end. Rust lends four kinds of value, on each server:
|
||||
|
||||
- **Decay rate**
|
||||
- **Population** — an animal or vehicle population
|
||||
- **Spawn scalar**
|
||||
- **Group permission** — a permission on a group, which the [permission
|
||||
mirror](/docs/rust/permissions-and-groups/) leaves alone while the lease holds
|
||||
|
||||
## Zone presets
|
||||
|
||||
**Admin → Rust zone presets** (`/admin/rust/zones`) holds named sets of ZoneManager flags and
|
||||
settings. Core's step editor has single-value fields, so a zone's flags are ticked here, and an
|
||||
event's *Open a zone* step offers the presets in its **Options** field. Picking one **copies** it
|
||||
into the step, so changing a preset later does not change an event already published.
|
||||
|
||||
The screen lists each server's ZoneManager version, how many flags it has, and whether domes are
|
||||
available. None ship with the module: what an arena is differs by server. Who is exempt from a
|
||||
flag is a `zonemanager.ignoreflag.*` permission, set on the permissions page. Where TruePVE is
|
||||
installed, a zone can also turn PvP protection off inside it, or name a TruePVE ruleset.
|
||||
|
||||
## Triggers
|
||||
|
||||
A phase can advance when something happens in the game, and the vocabulary of *something* is
|
||||
the module's trigger catalog. Rust's includes a server wiping, coming online or going offline, a
|
||||
new kills leader, a raided base, a guarded crate landing or being cleared, a treasure event
|
||||
starting or being won, and a RunicNPC boss appearing, changing phase or dying. The same catalog
|
||||
drives [Engagement rules](/docs/administration/engagement-rules/).
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§14 to §16 and §20 are leases, world verbs, rewards and the step editor on the wire;
|
||||
[`website/EVENTS.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/EVENTS.md)
|
||||
is core's event system, and
|
||||
[`modules/rust/PLAN_REDESIGNS.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust/PLAN_REDESIGNS.md)
|
||||
the design of the step editor and the zones.
|
||||
100
src/content/docs/docs/rust/index.mdx
Normal file
100
src/content/docs/docs/rust/index.mdx
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: The Rust module
|
||||
description: What the rust module adds to a site, the pieces that connect it to a Rust server running Oxide or Carbon, and where each page in this group fits.
|
||||
---
|
||||
|
||||
import platform from '../../../../data/platform.json';
|
||||
|
||||
The `rust` module turns a Runic Gateway site into a *Rust* community's site. It follows any
|
||||
number of Rust servers, each running **Oxide** or **Carbon**. Everything in this group is
|
||||
specific to it; the rest of the documentation is core, and describes the platform in core's
|
||||
own words.
|
||||
|
||||
## The pieces
|
||||
|
||||
| Piece | What it is | Release |
|
||||
|---|---|---|
|
||||
| **The module** | [`Module-Rust`](https://gitea.whitlocktech.com/RunicGateway/Module-Rust) — every Rust screen on the site | {platform.releases['Module-Rust']} |
|
||||
| **The sidecar** | [`Rust-Link`](https://gitea.whitlocktech.com/RunicGateway/Rust-Link), the **rust-link sidecar** — the only part of the bridge that listens on a network | {platform.games.rust.bundle.sidecar} |
|
||||
| **The bridge plugin** | [`Rust-Plugins`](https://gitea.whitlocktech.com/RunicGateway/Rust-Plugins) — `RunicGateway.cs`, one file that runs unchanged on Oxide and Carbon, plus its optional helpers | {platform.games.rust.bundle.plugin} |
|
||||
| **RunicNPC** | [`runicnpc-rust`](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust) — our NPC plugin, which the bridge drives for every NPC an event places. **Required** | {platform.games.rust.bundle.runicnpc} |
|
||||
|
||||
The sidecar, the bridge plugin and RunicNPC are installed together on the game host as a
|
||||
**bundle** — {platform.games.rust.bundle.tag} today, speaking protocol
|
||||
{platform.games.rust.protocol}. A bundle is an exact set CI has checked, never "the latest of
|
||||
each". **One sidecar serves one Rust server**: a community running six servers runs six
|
||||
sidecars, each with its own port, database and token, and the site holds six rows.
|
||||
|
||||
The bundle needs Oxide {platform.games.rust.bundle.oxideMin} or Carbon
|
||||
{platform.games.rust.bundle.carbonMin} or newer, and the uMod plugins
|
||||
{platform.games.rust.bundle.requiresPlugins.join(' and ')}. RunicNPC will not load without
|
||||
Kits, so a server without it cannot place NPCs in an event.
|
||||
|
||||
## What the module adds
|
||||
|
||||
Install it from **Admin → Modules** like any module — the install manifest is the
|
||||
`module-rust-<version>.json` asset on
|
||||
[its releases page](https://gitea.whitlocktech.com/RunicGateway/Module-Rust/releases). It
|
||||
needs Module API {platform.games.rust.module.coreApi}.
|
||||
|
||||
**For visitors**, at `/rust`: every server as it last reported itself, and a page per server
|
||||
with six tabs.
|
||||
|
||||
| Tab | What it shows |
|
||||
|---|---|
|
||||
| **Feed** | Kills, chat, comings and goings, the server's own story, RunicNPC bosses, and Rust's world events — cargo, helicopters, airdrops — as they happen |
|
||||
| **Leaderboard** | Per wipe: kills, deaths, NPC kills, structures, time played, and the chat titles players have earned |
|
||||
| **Online** | Who is on right now |
|
||||
| **Map** | The [live map](/docs/rust/the-live-map/): the server's own map picture, with layers |
|
||||
| **Wipes** | Every wipe the site has seen. The next one, once the server has a wipe schedule, is in the page's header |
|
||||
| **Clans** | Each clan's name, colour, score and members |
|
||||
|
||||
A server's **clans are the site's [Teams](/docs/administration/teams/)**, so a clan gets a
|
||||
page, a feed for its members, and core's Team notifications. Who may see a clan's roster —
|
||||
and who may see a named player at all — is an operator's choice, set on
|
||||
[Who sees who is online](/docs/rust/who-sees-who-is-online/).
|
||||
|
||||
**For players**, at `/player/rust`: linking a Steam account, what they can do in game, their
|
||||
NPC kills, coins and backpack. See [Linking Steam accounts](/docs/rust/linking-steam-accounts/).
|
||||
|
||||
**For staff**, seven screens in the admin sidebar, each covered in this group:
|
||||
|
||||
| Sidebar row | Page here |
|
||||
|---|---|
|
||||
| Rust servers | [Connect the site](/docs/rust/connect-the-site/) |
|
||||
| Rust permissions | [Permissions and groups](/docs/rust/permissions-and-groups/) |
|
||||
| Rust mod config | [Mod config from the site](/docs/rust/mod-config/) |
|
||||
| Rust visibility | [Who sees who is online](/docs/rust/who-sees-who-is-online/) |
|
||||
| Rust zone presets | [Events, zones and rewards](/docs/rust/events-zones-and-rewards/) |
|
||||
| Rust NPC profiles, Rust NPC placements | RunicNPC, which has its own pages |
|
||||
|
||||
The module also adds a **Rust** section to each user's page under **Admin → Users** (their
|
||||
linked Steam accounts, permissions, coins and backpack), Rust steps and triggers to core's
|
||||
[Scheduled events](/docs/administration/events/), and Rust triggers to
|
||||
[Engagement rules](/docs/administration/engagement-rules/) — *your base was raided*, *a server
|
||||
wiped*, *a new kills leader* and more.
|
||||
|
||||
Its capabilities are {platform.games.rust.module.capabilities.join(', ')}.
|
||||
|
||||
Until a server is connected, its pages exist and report it as offline. That is the same
|
||||
thing the public site does when a server goes down, and it is designed to be unremarkable:
|
||||
every list, count and last-reported time comes from what the site stored.
|
||||
|
||||
## In this group
|
||||
|
||||
| Page | Read it when |
|
||||
|---|---|
|
||||
| [Install the bridge](/docs/rust/install-the-bridge/) | Setting a Rust server up: the installer, a Pterodactyl egg, or by hand |
|
||||
| [Connect the site](/docs/rust/connect-the-site/) | Adding the server to the site, and the rest of its settings |
|
||||
| [Verify the bridge](/docs/rust/verify-the-bridge/) | Proving the server, the sidecar and the site are really talking |
|
||||
| [Linking Steam accounts](/docs/rust/linking-steam-accounts/) | Joining a player's Steam account to their site account |
|
||||
| [Permissions and groups](/docs/rust/permissions-and-groups/) | Granting permissions and managing groups from the site |
|
||||
| [Mod config from the site](/docs/rust/mod-config/) | Editing your plugins' config files without a file manager |
|
||||
| [Who sees who is online](/docs/rust/who-sees-who-is-online/) | Deciding what the public sees: names, rosters, map layers |
|
||||
| [Events, zones and rewards](/docs/rust/events-zones-and-rewards/) | Running scheduled events on a Rust server |
|
||||
| [Optional plugins](/docs/rust/optional-plugins/) | What each supported uMod plugin adds, and two that catch operators out |
|
||||
| [The live map](/docs/rust/the-live-map/) | What the map shows, and where the picture comes from |
|
||||
| [Troubleshooting](/docs/rust/troubleshooting/) | Something on the game side is wrong |
|
||||
| [sidecar.toml](/docs/rust/sidecar-toml/) | Changing the sidecar's binds, token, store or history |
|
||||
| [RunicGateway.json](/docs/rust/runicgateway-json/) | Changing the bridge plugin's own settings and bounds |
|
||||
| [Event kinds](/docs/rust/event-kinds/) | Knowing what the server can tell the site, and who may see each |
|
||||
161
src/content/docs/docs/rust/install-the-bridge.mdx
Normal file
161
src/content/docs/docs/rust/install-the-bridge.mdx
Normal file
@@ -0,0 +1,161 @@
|
||||
---
|
||||
title: Install the bridge
|
||||
description: Setting a Rust server up for the site — with the installer, from a Pterodactyl egg, or by hand — and what each puts where.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import platform from '../../../../data/platform.json';
|
||||
|
||||
This happens on the machine that runs your Rust server. It puts three things there — the
|
||||
bridge plugin, the sidecar and RunicNPC — from one bundle, {platform.games.rust.bundle.tag}
|
||||
today, and prints what the site needs. It never contacts your site, and it never starts or
|
||||
stops your game server.
|
||||
|
||||
```
|
||||
Rust server ──loopback, dials out──► rust-link sidecar ──HTTP + WebSocket──► your site
|
||||
```
|
||||
|
||||
The game server opens no port for the bridge: the plugin dials the sidecar on `127.0.0.1`.
|
||||
|
||||
## Before you begin
|
||||
|
||||
- **Oxide or Carbon is installed, and the server has started once with it**, so its
|
||||
directories exist. A vanilla server has nothing to load a plugin with. Oxide
|
||||
{platform.games.rust.bundle.oxideMin} or Carbon {platform.games.rust.bundle.carbonMin} or
|
||||
newer — never both in one tree.
|
||||
- **Kits and ZoneManager**, from uMod. The bridge runs without them and names what is missing,
|
||||
but rewards and zones need them, and **RunicNPC will not load without Kits**. Nothing here
|
||||
installs them.
|
||||
- **The site has the Rust module**, and you are an administrator on it.
|
||||
|
||||
## Three ways, one result
|
||||
|
||||
| | Use it when |
|
||||
|---|---|
|
||||
| [**The installer**](#the-installer) | The server runs on Linux with systemd, or on Windows |
|
||||
| [**The Pterodactyl egg**](#the-pterodactyl-egg) | The server runs on a Pterodactyl panel |
|
||||
| [**By hand**](#by-hand) | Neither fits, or you want to place every file yourself |
|
||||
|
||||
## The installer
|
||||
|
||||
The [Runic Gateway installer](/docs/reference/installer-cli/), {platform.releases.installer},
|
||||
told it is setting up Rust. Run it as root, or from an elevated PowerShell:
|
||||
|
||||
```bash
|
||||
sudo ./runicgateway-installer-linux-x86_64 install --game rust --rust /srv/rust --server-id main
|
||||
```
|
||||
|
||||
```powershell
|
||||
.\runicgateway-installer-windows-x86_64.exe install --game rust --rust D:\rust --server-id main
|
||||
```
|
||||
|
||||
`--rust` is the server root, the directory holding `RustDedicated`. **`--server-id` is
|
||||
required**: it is the id the site will know this server by — lowercase letters, digits and
|
||||
`-`, up to 64 — and it is permanent (see [Connect the site](/docs/rust/connect-the-site/)).
|
||||
Add `--verify` to see everything it would do and write nothing.
|
||||
|
||||
A run resolves the bundle, detects Oxide or Carbon from the server itself, and refuses a tree
|
||||
that has both or neither. It writes the plugin's config with just `ServerId` and `Port`,
|
||||
**only if there is none** — an existing config is never rewritten, and one that names a
|
||||
different server stops the run. It installs the sidecar and registers its service, places the
|
||||
plugin, its helpers and RunicNPC, and prints what the site needs. Both frameworks load a
|
||||
plugin the moment it lands, so on a running server the bridge comes up at once.
|
||||
|
||||
**More than one server on a host:** run it once per server, each with its own `--rust` and
|
||||
`--server-id`. Each gets its own service, config, database, game port (from 7799) and web
|
||||
port (from 8090), and they share one sidecar binary.
|
||||
|
||||
| | Linux | Windows |
|
||||
|---|---|---|
|
||||
| The sidecar binary, shared | `/usr/bin/runicgateway-rust-link` | `%ProgramFiles%\RunicGateway\rust-link-sidecar.exe` |
|
||||
| One server's sidecar config — **holds its token** | `/etc/runicgateway/rust/<id>.toml`, mode 600 | `%ProgramData%\RunicGateway\rust\<id>\sidecar.toml` |
|
||||
| One server's service | `runicgateway-rust@<id>.service` | `RunicGatewayRust-<id>` |
|
||||
|
||||
The full list, including the database and the install record, is in the
|
||||
[Rust bridge install guide](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INSTALL.md#where-everything-lands).
|
||||
|
||||
## The Pterodactyl egg
|
||||
|
||||
Each Rust-Link release carries **`egg-rust-runicgateway.json`**: the panel's *Rust Autowipe*
|
||||
egg with the bridge added. A panel administrator imports it under **Admin → Nests → Import
|
||||
Egg**. When you create a server from it, these variables matter:
|
||||
|
||||
| Variable | |
|
||||
|---|---|
|
||||
| **Modding Framework** | `oxide` or `carbon`. `vanilla` installs no bridge |
|
||||
| **Runic Gateway: server id** | The id on the site. Read **once**, when the plugin writes its first config; changing it later changes nothing |
|
||||
| **Runic Gateway: sidecar port** | **One of this server's allocations.** A port the server was not allocated binds, but is never reachable |
|
||||
| **Runic Gateway: sidecar token** | Leave blank, and the sidecar generates one. Anyone who can see this server's startup variables can read a token typed here |
|
||||
| **Runic Gateway: bundle** | Blank takes the current bundle; a tag pins one |
|
||||
|
||||
The install checks every file against the bundle's checksums before it places any of them.
|
||||
The **first boot** prints the generated token in the console, **once**, and a line with what
|
||||
the site needs:
|
||||
|
||||
```
|
||||
[rust-link] Admin -> Rust -> Servers: server id 'main', sidecar URL http://<this node's address>:21009 (listening on 0.0.0.0:21009)
|
||||
```
|
||||
|
||||
The sidecar runs inside the server's container and stops with it.
|
||||
|
||||
- **Updating is a reinstall**, with *Regen Server* off to keep the map. A restart never
|
||||
fetches anything, so it can never change the protocol under a site that has not been
|
||||
updated.
|
||||
- **A wipe keeps the bridge's history.** The egg moves `rust-link/` out of the way while
|
||||
`REMOVE_FILES` runs, so no delete list can reach the database or the token.
|
||||
|
||||
## By hand
|
||||
|
||||
1. **Pick the bundle.** The current one is
|
||||
[`v2/rust/current.json`](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/bundles/v2/rust/current.json)
|
||||
on the installer's `bundles` branch. It names the sidecar for your platform, the plugin
|
||||
tarball and RunicNPC's, each with a `sha256`.
|
||||
2. **Download and verify** each against its checksum.
|
||||
3. **The plugin:** copy `RunicGateway.cs` into `oxide/plugins/` or `carbon/plugins/`, with
|
||||
every other `.cs` the tarball's `manifest.json` lists beside it, and `RunicNPC.cs` from
|
||||
its own tarball. To name the server, create `oxide/config/RunicGateway.json` (Carbon:
|
||||
`carbon/configs/RunicGateway.json`) first, holding `{ "ServerId": "<id>", "Port": 7799 }`.
|
||||
Without it the plugin calls the server `main`.
|
||||
4. **The sidecar:** put the binary somewhere stable, give it a
|
||||
[`sidecar.toml`](/docs/rust/sidecar-toml/), and run
|
||||
`rust-link-sidecar --print-config --config <file>` once — it generates the token, saves
|
||||
it, and prints it.
|
||||
5. **Run it** under your supervisor with `--config <file>`, as an unprivileged user that can
|
||||
write the database directory. On Windows the same `.exe` runs as a service.
|
||||
|
||||
<Aside type="caution" title="Do not create RunicNPC's data folder yourself">
|
||||
RunicNPC makes `data/RunicNPC/` on its first load. One made from outside the game — a panel's
|
||||
file manager, say — may not be writable by it.
|
||||
</Aside>
|
||||
|
||||
## The helpers beside the plugin
|
||||
|
||||
The bundle ships three optional helpers beside the plugin, and the installer and the egg
|
||||
place all three. By hand, the tarball's `manifest.json` lists them in `files`:
|
||||
|
||||
- **`RunicGatewayZones.cs`** lets ZoneManager count a player already standing in an event
|
||||
zone when it opens or comes back after a restart, and enforce the flags of a zone opened
|
||||
after it loaded. Without it, events still score everybody, but a zone's flags — PvP off,
|
||||
no building — may not hold.
|
||||
- **`RunicGatewayDomes.cs`** lets the bridge ask ZoneDomes for a dome over an event zone.
|
||||
Without it, zones open without one.
|
||||
- **`RunicGatewayRaids.cs`** lets an event spawn a RaidableBases base of its own and despawn
|
||||
just that base when the event ends; see
|
||||
[Optional plugins](/docs/rust/optional-plugins/#raidablebases).
|
||||
|
||||
## Day two
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `doctor --game rust` | Per server: the framework, whether each deployed file is still the one deployed, RunicNPC (a **failure** when missing — it is required), the required uMod plugins, the service, and `/health` through to *plugin connected* |
|
||||
| `update --game rust` | Moves the sidecar and every server's plugin to the current bundle, together — they share one binary |
|
||||
| `uninstall --game rust` | Removes the service, the plugin, its helpers and RunicNPC. **Keeps the plugin's config** and RunicNPC's data, because they name the server. `--purge` also removes the token and the database |
|
||||
|
||||
Next: [Connect the site](/docs/rust/connect-the-site/).
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/INSTALL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INSTALL.md)
|
||||
is the operator guide, and its flags, paths and egg variables are the authority;
|
||||
[`installer/INSTALL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/installer/INSTALL.md)
|
||||
covers the installer itself.
|
||||
79
src/content/docs/docs/rust/linking-steam-accounts.mdx
Normal file
79
src/content/docs/docs/rust/linking-steam-accounts.mdx
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Linking Steam accounts
|
||||
description: How a player joins their Steam account to their site account — a code from /link in game, or Steam sign-in — and how staff undo a wrong link.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
A Rust player is a Steam account. A site member is a site account. **Linking** joins the two,
|
||||
and it is what lets the site say *this member is that player*: their own page shows their
|
||||
kills, coins and backpack, a clan becomes a [Team](/docs/administration/teams/) whose members
|
||||
the site knows, and an event can reward a member by name.
|
||||
|
||||
## Two ways to link
|
||||
|
||||
**From the game, with a code.** The player page at `/player/rust` walks a player through it:
|
||||
|
||||
1. Join any of the community's Rust servers and type **`/link`** in chat.
|
||||
2. The server answers with a **six-character code** that only that player can see. It lasts
|
||||
five minutes.
|
||||
3. Type it on `/player/rust`. It works once.
|
||||
|
||||
Typing a code that only reached one player into a signed-in browser is the proof that the
|
||||
person at the browser is the person in the game. **The code never crosses the bridge**: what
|
||||
the server tells the site is only that somebody asked for one.
|
||||
|
||||
**By signing in with Steam.** When an administrator has turned on Steam under
|
||||
[Authentication](/docs/administration/authentication/#steam), a member who links Steam to
|
||||
their site account from their account page has linked that Steam account for Rust too. It is
|
||||
the same link, reached through Steam's own sign-in rather than through the game.
|
||||
|
||||
## What a link is
|
||||
|
||||
- **Fleet-wide.** A code from any of your servers links for all of them, and a member's
|
||||
totals on their page are summed across every server and every wipe.
|
||||
- **One owner at a time.** A Steam account belongs to one site account. If somebody links the
|
||||
wrong account, the site refuses to move it — including through Steam sign-in, which refuses a
|
||||
Steam account that is already another member's Rust link.
|
||||
- **Kept by the site, not the game.** Nothing about a link is stored on the game host.
|
||||
- **A member may hold several Steam accounts.**
|
||||
|
||||
## Undoing a link
|
||||
|
||||
- **The player:** **`/unlink`** in game asks the site to let go.
|
||||
- **Staff:** **Admin → Users**, open the member, and the **Rust** section lists each linked
|
||||
Steam account with an **Unlink** button. It is the way back for a player who linked the
|
||||
wrong account and cannot reach it in game, and it is recorded in the activity log.
|
||||
|
||||
<Aside type="note" title="Unlinking Steam sign-in leaves the Rust link in place">
|
||||
Removing Steam from a member's sign-in methods stops them signing in with it, and nothing
|
||||
else. The Rust link is removed on the Rust side — `/unlink`, or the Rust section of the
|
||||
member's page — so a member who only meant to stop using Steam to sign in does not lose
|
||||
their history.
|
||||
</Aside>
|
||||
|
||||
## What a linked player sees
|
||||
|
||||
On `/player/rust`: their linked Steam accounts, what they can do in game (the permissions
|
||||
the site has given them on each server), the NPCs they have killed this wipe, their coins on
|
||||
each server, and their backpack — once something is linked. Staff are sent to the admin
|
||||
area rather than the player portal, and see the same facts for anybody from that member's
|
||||
page.
|
||||
|
||||
## What staff see
|
||||
|
||||
On a member's page under **Admin → Users**, the Rust section shows each linked Steam account
|
||||
with when it was linked and last played, and kills, deaths and time played per server. Below
|
||||
it, staff can grant the member permissions, deposit, withdraw or set their coins on each
|
||||
server, and open their backpack.
|
||||
|
||||
A link request and an unlink are **staff-only** [event kinds](/docs/rust/event-kinds/):
|
||||
together they name a Steam account beside a site account's activity, and *this player is
|
||||
that person* is not a public page's business.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§9 is the link on the wire, and
|
||||
[`website/STEAM_SIGNIN.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/STEAM_SIGNIN.md)
|
||||
is Steam sign-in, including how it meets the Rust link.
|
||||
76
src/content/docs/docs/rust/mod-config.mdx
Normal file
76
src/content/docs/docs/rust/mod-config.mdx
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Mod config from the site
|
||||
description: Admin → Rust mod config — editing your plugins' config files on the game host from the site, with a backup, a watched reload, and an automatic undo.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
**Admin → Rust mod config** (`/admin/rust/config`) edits the configuration files on the game host
|
||||
itself, read live through the bridge. It works for whatever plugins you have installed, because
|
||||
it generates its form from the values it finds in each file.
|
||||
|
||||
## What it lists
|
||||
|
||||
Pick a server, and the screen names the directory it is reading — `oxide/config` or
|
||||
`carbon/configs`, or wherever your framework was told to keep them — and lists the files there,
|
||||
**grouped by the plugin each one probably belongs to**, with whether that plugin is loaded and
|
||||
its version.
|
||||
|
||||
**The data directory is not listed, on purpose.** `oxide/data` and `carbon/data` hold live state
|
||||
— kit cooldowns, zone definitions, the permission store itself — not settings. Editing them from
|
||||
a web form edits your players' cooldowns, and the running plugin writes over the change on its
|
||||
next save anyway. The one exception is RaidableBases' **profiles**, which live under its data
|
||||
directory but are settings, and are listed beside its config as *Profile · …*.
|
||||
|
||||
## Editing a file
|
||||
|
||||
Open a file and it shows as a form (**Settings**), or as **Raw JSON** for a change the form does
|
||||
not suit. Then choose what to **Reload** — the site suggests the plugin the file belongs to, and
|
||||
*nothing — just write the file* is an option. A folder name is convention rather than contract,
|
||||
so the guess is a field you can change, not an assumption.
|
||||
|
||||
**Save and reload** then:
|
||||
|
||||
1. backs the file up,
|
||||
2. writes it,
|
||||
3. reloads the plugin you named, and watches the reload,
|
||||
4. and if the plugin does not come back, **puts the old file back on its own**, and shows you
|
||||
the line from the server's log that says why.
|
||||
|
||||
A typo costs you a few seconds, not a plugin. The answer you see is one of:
|
||||
|
||||
- *Saved, and the plugin reloaded.*
|
||||
- *The plugin did not come back, so the old file was put back automatically.*
|
||||
- *…and it did not come back on the old file either. It is down: check the server console.* —
|
||||
the old file was broken too, which is worth knowing before anything else.
|
||||
- *The server never said how the reload went* — the bridge itself was reloaded, or the link
|
||||
dropped. Re-read the file to see what is on disk.
|
||||
|
||||
A reload fails on evidence — the framework's log saying the plugin did not compile or start —
|
||||
and a reload that says nothing at all is called lost only once a fixed ceiling has passed.
|
||||
|
||||
**Recent changes**, at the bottom, is every save, including the ones that did not land: what
|
||||
changed, field by field, and whether it was applied or rolled back.
|
||||
|
||||
<Aside type="caution" title="The bridge's own file is special">
|
||||
`RunicGateway.json` is editable here, except **`Host`, `Port` and `ServerId`**, which are shown
|
||||
read-only. Changing them from the site would cut the link carrying the change, or strand every
|
||||
row the site holds for the server — edit them on the host. And the bridge cannot be reloaded
|
||||
from here, for the same reason. See [RunicGateway.json](/docs/rust/runicgateway-json/).
|
||||
</Aside>
|
||||
|
||||
<Aside type="caution" title="RaidableBases: restart, never reload">
|
||||
Change RaidableBases' config, then **restart the server** instead of reloading the plugin. A
|
||||
reload can leave its live raids broken until a restart. See
|
||||
[Optional plugins](/docs/rust/optional-plugins/#raidablebases).
|
||||
</Aside>
|
||||
|
||||
`rg.config` at the server console prints which directory the site is reading, the log it
|
||||
watches, a reload in progress, and what the last write from the site did.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§11 and §19.1 are configuration on the wire;
|
||||
[`modules/rust/OPERATING.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust/OPERATING.md#plugin-configuration-plugins-configuration)
|
||||
has uMod's own page on plugin configuration.
|
||||
93
src/content/docs/docs/rust/optional-plugins.mdx
Normal file
93
src/content/docs/docs/rust/optional-plugins.mdx
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Optional plugins
|
||||
description: The uMod plugins the Rust module recognises beyond the required two, what each one adds to the site, and the two — RaidableBases and Guarded Crate — that catch operators out.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import platform from '../../../../data/platform.json';
|
||||
|
||||
The bridge needs {platform.games.rust.bundle.requiresPlugins.join(' and ')}, and RunicNPC. Everything
|
||||
below is optional: install what your server wants, and the site notices. The bridge reports what it
|
||||
finds when it connects and whenever a plugin loads or unloads, and **Optional mods** on a row of
|
||||
[Admin → Rust servers](/docs/rust/connect-the-site/#the-rest-of-a-servers-settings) prints it —
|
||||
each plugin's version, and what it lets the site do on that server.
|
||||
|
||||
**Runic Gateway never ships or modifies any of these.** They are their authors' plugins, installed
|
||||
the way uMod's own guide describes, and a server without one simply lacks that feature.
|
||||
|
||||
| Plugin | What the site does with it |
|
||||
|---|---|
|
||||
| **BetterChat** | Shows [chat titles](/docs/rust/connect-the-site/#chat-titles) and [group chat styles](/docs/rust/permissions-and-groups/#chat-styles) in game chat. Titles still show on the leaderboard without it |
|
||||
| **PopupNotifications** | Lets news, event lines and zone messages arrive as a popup. Without it a popup falls back to chat, or is refused with a reason |
|
||||
| **Economics** | Coins: the *Pay coins* step, the *Richest* title, and balances on a member's page that staff can adjust |
|
||||
| **Backpacks** | Shows a player's backpack, and lets an event lend a bigger size |
|
||||
| **TruePVE** | Lets an event's zone turn PvP protection off, or name a ruleset |
|
||||
| **Dynamic PVP** | Its PvP zones — around a downed helicopter, a Bradley, cargo — appear in the feed and on the map |
|
||||
| **Dangerous Treasures** | Its events appear in the feed and on the map, earn the *Treasure Hunter* title, and a step may start one |
|
||||
| **Guarded Crate** | Its crates appear in the feed and on the map, earn the *Warden* title, and a step may place one. [Needs NpcSpawn](#guarded-crate) |
|
||||
| **RaidableBases** | Its bases appear in the feed and on the map, earn the *Warlord* title, and a step may spawn one that is the event's own. [Read this first](#raidablebases) |
|
||||
| **ZLevels Remastered** | An event may lend its XP boosts, which are permissions |
|
||||
| **ZoneDomes** | A dome over an event's zone, through the bridge's domes helper |
|
||||
|
||||
PopupNotifications has a recommended look — a banner across the top of the screen — which the
|
||||
installer and the egg write only where its config is missing or still its own defaults. The
|
||||
[install guide](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INTEGRATION.md#24-the-recommended-popupnotifications-look-optional)
|
||||
has the config.
|
||||
|
||||
**Clans is not on the list, deliberately.** A server's clans come from Rust's own clan system,
|
||||
which that plugin never touches, so installing it would split your players across two unrelated
|
||||
clan systems.
|
||||
|
||||
## RaidableBases
|
||||
|
||||
Either edition works, the paid or the free one. The bridge's **raids helper**,
|
||||
`RunicGatewayRaids.cs`, installed beside the plugin by default, is what lets an event spawn a base
|
||||
of its own — at a spot, with its own settings and lock — and despawn only that base when the event
|
||||
ends. Without it a base an event spawned despawns on its own timer. Difficulty, profile and base
|
||||
names in the step editor come from RaidableBases itself, so they are whatever your copy has.
|
||||
|
||||
Two of RaidableBases' own behaviours catch operators out:
|
||||
|
||||
<Aside type="caution" title="Change its config with a restart, never a reload">
|
||||
After a reload of RaidableBases on a running server, its list of live raids can hold an empty
|
||||
entry, and every hook that walks the list fails: damage to a base, its SAM sites, players'
|
||||
commands, and the purchase command, so a purchase does nothing and charges nothing. Only a full
|
||||
server restart clears it. It is RaidableBases' own bug. Edit its config — from
|
||||
[Mod config](/docs/rust/mod-config/) with *nothing* chosen to reload, or on the host — then restart
|
||||
the server.
|
||||
</Aside>
|
||||
|
||||
<Aside type="caution" title="Back up before switching editions">
|
||||
Putting the free edition in place of the paid one rewrites the profiles a paid base pack installed,
|
||||
and drops their bases from them. Going back to the paid edition does not bring them back. Back up
|
||||
RaidableBases' config and data folders first.
|
||||
</Aside>
|
||||
|
||||
## Guarded Crate
|
||||
|
||||
Guarded Crate **needs NpcSpawn** for its guards, and NpcSpawn is not on uMod: it is a free download
|
||||
from Codefling. Install it before Guarded Crate:
|
||||
|
||||
1. Put `NpcSpawn.cs` in the plugins directory.
|
||||
2. Put the archive's `data/Images/` files in the data directory's `Images` folder. **Without them
|
||||
NpcSpawn unloads itself.**
|
||||
3. Leave `data/NpcSpawn/` for NpcSpawn to create. A folder made by a panel's file manager may not be
|
||||
writable by the game.
|
||||
4. Install Guarded Crate from uMod.
|
||||
|
||||
**NpcSpawn contacts its author's server when it loads**: it calls a version server and downloads
|
||||
clothing images. If your server may not reach the internet, or you do not want it to, know that
|
||||
before installing it.
|
||||
|
||||
Without NpcSpawn, the *Place a guarded crate* step is refused with a reason — the bridge checks,
|
||||
because Guarded Crate would otherwise drop a crate with no guards. Its events are rows in its data
|
||||
file (Easy, Medium, Hard and Elite as it ships), and the site offers whatever names that file holds.
|
||||
**A crate placed by an event is kept when the event ends**: Guarded Crate has no way to end one
|
||||
event without ending them all.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`modules/rust/OPERATING.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust/OPERATING.md)
|
||||
holds the RaidableBases and Guarded Crate notes in full;
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§18 and §20 are each plugin on the wire.
|
||||
90
src/content/docs/docs/rust/permissions-and-groups.mdx
Normal file
90
src/content/docs/docs/rust/permissions-and-groups.mdx
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Permissions and groups
|
||||
description: Admin → Rust permissions — the site holds every server's permissions and groups, pushes them into Oxide or Carbon, and decides what happens to a change made in game.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
**Admin → Rust permissions** (`/admin/rust`) is where the site holds every permission and group
|
||||
on each server: what was there before the site, what is made here, and what is changed in the
|
||||
game. It reads each server's own permission store and pushes the site's set back into it, so
|
||||
every plugin you already run honours it — Kits, ZoneManager, anything that asks Oxide or
|
||||
Carbon whether a player has a permission. Nothing is configured twice, and nothing is asked of
|
||||
those plugins.
|
||||
|
||||
## The first sync imports
|
||||
|
||||
A server's first sync **imports everything** in its store, so taking the site into use loses
|
||||
nothing an operator had already set up at the console. Until then the server is marked *not
|
||||
imported yet*. After that, the site's set is the one that holds.
|
||||
|
||||
**A wipe loses nothing.** The site pushes the whole set again when the server comes back, so a
|
||||
wipe script that clears the data directory costs a minute, not your permissions.
|
||||
|
||||
## In-game changes
|
||||
|
||||
An operator will still grant something at the console during an incident. What happens to it is
|
||||
a per-server choice:
|
||||
|
||||
| Mode | A change made in the game |
|
||||
|---|---|
|
||||
| **auto-adopt** | Becomes the site's own, for that server |
|
||||
| **adopt** | Waits under *Waiting for a person*, to be adopted or undone |
|
||||
| **revoke** | Is undone at the next sync |
|
||||
|
||||
The chip beside the mode says when the server last synced. **Sync now** pushes at once.
|
||||
|
||||
## Players
|
||||
|
||||
The **Players** tab lists everybody who holds something on the selected server; pressing Enter in
|
||||
its search reaches every player the server has seen. A player shows their site account, when they
|
||||
have [linked one](/docs/rust/linking-steam-accounts/).
|
||||
|
||||
Pick one, and their permissions are listed by the plugin that registers them, with **Grant all**
|
||||
and **Revoke all** per plugin. Tick **Grant on every server** to make a grant fleet-wide rather
|
||||
than for this server only. Chips on a permission explain what you are looking at:
|
||||
|
||||
| Chip | Meaning |
|
||||
|---|---|
|
||||
| *not registered here* | No loaded plugin on this server registers it, so it cannot be granted — Oxide and Carbon silently do nothing for an unknown name. Load the plugin and the grant lands by itself |
|
||||
| *revoked here only* | Granted on every other server, and taken away on this one |
|
||||
| *from an event* | An event gave it, and the event's end takes it back |
|
||||
| *waiting for their first connection* | The server has never seen this player. A grant to them lands when they first join; every sync pushes it again |
|
||||
|
||||
## Groups
|
||||
|
||||
The **Groups** tab lists the server's groups with their member counts, and **Create** makes a
|
||||
new one. Each group has four tabs:
|
||||
|
||||
- **Permissions**, laid out as a player's are.
|
||||
- **Players** — who is in it. Every player who connects is in `default`, so it lists nobody.
|
||||
A player the server has never seen cannot be put in a group yet; the site says the membership
|
||||
is waiting, and it lands on their first connection.
|
||||
- **Settings** — the group's title, its rank and its parent group, whose permissions it
|
||||
inherits.
|
||||
- **Servers.** A group belongs to one server unless it is **shared**. A shared group carries the
|
||||
same permissions and players on every server it is on — including servers added later, if you
|
||||
tick that — and a change made in one game gives that server its own copy.
|
||||
|
||||
### Chat styles
|
||||
|
||||
A group's **Settings** tab has a **Chat style (BetterChat)** section. Give a group a style and
|
||||
BetterChat, where it is installed, shows its members' titles, colours and sizes as you set them
|
||||
here, with a priority deciding between a player's groups. A group with a style can also be the
|
||||
**announcement voice** on [Admin → Rust servers](/docs/rust/connect-the-site/#chat-titles), so
|
||||
news and event lines in game chat wear its title.
|
||||
|
||||
<Aside type="note" title="rg.perms at the server console">
|
||||
Prints what the last sync did. It is the fastest way to tell *that permission does not exist
|
||||
here* from *that player has never been seen here* — both look like "the site says they have it
|
||||
and the game disagrees" from the other end.
|
||||
</Aside>
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§10 and §19.9 are the permission mirror on the wire;
|
||||
[`modules/rust/OPERATING.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust/OPERATING.md#permissions-plugins-permissions)
|
||||
has uMod's own guide to permissions and groups, and
|
||||
[`CARBON.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust/CARBON.md)
|
||||
how Carbon's store differs.
|
||||
70
src/content/docs/docs/rust/runicgateway-json.mdx
Normal file
70
src/content/docs/docs/rust/runicgateway-json.mdx
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: RunicGateway.json
|
||||
description: The bridge plugin's own config file on the Rust server — every key, which three the site will not edit, and the event bounds that refuse rather than trim.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import { rustPluginConfig } from '../../../../data/reference.mjs';
|
||||
|
||||
The bridge plugin's config, written by the framework as `oxide/config/RunicGateway.json`, or
|
||||
`carbon/configs/RunicGateway.json` on Carbon. The plugin fills in every key it is missing on
|
||||
load, so a config written by an older version shows the newer keys after the first load with the
|
||||
new one — which is what lets [Mod config](/docs/rust/mod-config/) show a switch before anybody needs
|
||||
it.
|
||||
|
||||
**Where the first one comes from.** The installer writes one holding just `ServerId` and `Port`,
|
||||
and only when there is none. On a Pterodactyl egg, the plugin's first load takes the server id from
|
||||
the `RUSTLINK_SERVER_ID` variable. After that the file is canonical, and changing either source
|
||||
changes nothing.
|
||||
|
||||
## The keys
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Key</th><th>What it is for</th></tr></thead>
|
||||
<tbody>
|
||||
{Object.entries(rustPluginConfig).flatMap(([group, keys]) => [
|
||||
<tr key={group}><th colspan="2">{group}</th></tr>,
|
||||
...Object.entries(keys).map(([name, k]) => (
|
||||
<tr key={name}>
|
||||
<td><code>{name}</code>{k.readOnlyFromSite ? <><br /><small>read-only from the site</small></> : null}</td>
|
||||
<td>{k.why}</td>
|
||||
</tr>
|
||||
)),
|
||||
])}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
This list is checked against the plugin's own source on every build.
|
||||
|
||||
<Aside type="caution" title="Three keys are the host's">
|
||||
`Host`, `Port` and `ServerId` are read-only in the site's config editor. Changing the first two
|
||||
from the site would cut the link carrying the change; changing `ServerId` would strand every row
|
||||
the site holds for this server. **`ServerId` is permanent** — it is not the hostname, so a server
|
||||
can be renamed for a season without losing its history.
|
||||
</Aside>
|
||||
|
||||
## The event bounds
|
||||
|
||||
`EventsEnabled` ships **off**. While it is off the site may still list and give back what events
|
||||
hold, so switching it off never strands anything; it may not borrow, place, count or say anything
|
||||
new. See [Events, zones and rewards](/docs/rust/events-zones-and-rewards/).
|
||||
|
||||
Each `EventsMax…` key is how much **one step** may do on this server — or, for
|
||||
`EventsMaxWorldCalls`, one run, and for `EventsMaxTallies`, the whole server at once. Two rules
|
||||
hold for all of them:
|
||||
|
||||
- **An operator may lower a bound, never raise it** past the plugin's own ceiling. A value above it
|
||||
is brought down to it when the plugin loads.
|
||||
- **A step over a bound is refused, never trimmed to fit.** An event that does less than its
|
||||
author wrote, quietly, is worse than one that says it cannot.
|
||||
|
||||
The two `Map…` keys are different: a map answer over its bound is cut short and says so, because a
|
||||
read has nobody to refuse.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
The `PluginConfig` class in
|
||||
[`RunicGateway.cs`](https://gitea.whitlocktech.com/RunicGateway/Rust-Plugins/src/branch/main/overlay/oxide/plugins/RunicGateway.cs)
|
||||
is the authority;
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§6.1 describes the file and §14 to §17 each key as it arrived.
|
||||
83
src/content/docs/docs/rust/sidecar-toml.mdx
Normal file
83
src/content/docs/docs/rust/sidecar-toml.mdx
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: sidecar.toml
|
||||
description: The rust-link sidecar's whole configuration — six keys and the six environment variables that override them — and why the file is written rather than shipped.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import { rustSidecarConfig, rustSidecarEnv } from '../../../../data/reference.mjs';
|
||||
|
||||
The rust-link sidecar's configuration. Like every sidecar on this platform it is small on
|
||||
purpose: the sidecar forwards, and policy lives on the site where an administrator can see it.
|
||||
**One file per Rust server**, because one sidecar serves one server.
|
||||
|
||||
## The file is written, not shipped
|
||||
|
||||
The sidecar **writes its config on first run** if there is none, with a generated auth token, and
|
||||
nothing is compiled into the binary. `rust-link-sidecar --print-config --config <file>` resolves
|
||||
the config exactly as a start would — writing it and generating the token if needed — and prints
|
||||
the result, **token included**, which is the point.
|
||||
|
||||
Which file: `--config <file>`, else `$RUSTLINK_CONFIG`, else `sidecar.toml` in the working
|
||||
directory. **Relative paths resolve beside the config file**, not the working directory, so a
|
||||
database configured as `rust-link.db` lives next to its config wherever the service manager
|
||||
started it. The installer's files are under `/etc/runicgateway/rust/`, or
|
||||
`%ProgramData%\RunicGateway\rust\` on Windows; the egg's is `rust-link/sidecar.toml` in the server's
|
||||
directory.
|
||||
|
||||
<Aside type="caution" title="Authentication is always on">
|
||||
A blank token is not "no authentication": it is generated and written back, so the web side is
|
||||
authenticated from first boot. There is no way to turn it off.
|
||||
</Aside>
|
||||
|
||||
## The keys
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Key</th><th>What it is for</th></tr></thead>
|
||||
<tbody>
|
||||
{Object.entries(rustSidecarConfig).map(([name, why]) => (
|
||||
<tr key={name}><td><code>{name}</code></td><td>{why}</td></tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
**`[game].bind` stays on loopback.** There is no token on the game link — the plugin and the
|
||||
sidecar share a host, and `127.0.0.1` is the authentication. Moving it to a routable address puts
|
||||
an unauthenticated command channel for your game server on the network. `[web].bind` is the one
|
||||
that may move, behind TLS and a firewall.
|
||||
|
||||
**`[store].retain_days` bounds the sidecar's copy, not the history.** The permanent record — per-wipe
|
||||
totals that survive a wipe — is in the site's own tables, so shortening it loses recent raw detail
|
||||
and never a player's history. Keep the sidecar's files out of any wipe script's delete list all
|
||||
the same: the config holds the token, and the database the recent history.
|
||||
|
||||
## Environment variables
|
||||
|
||||
Each overrides one key. **An empty variable is ignored**, so a panel can declare one and leave it
|
||||
blank without blanking the saved value.
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Variable</th><th>Overrides</th></tr></thead>
|
||||
<tbody>
|
||||
{Object.entries(rustSidecarEnv).map(([name, key]) => (
|
||||
<tr key={name}><td><code>{name}</code></td><td><code>{key}</code></td></tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Both lists are checked against the sidecar's own source on every build, so a key or a variable
|
||||
added upstream turns this page red rather than quietly going undocumented.
|
||||
|
||||
## What is *not* in here
|
||||
|
||||
- **No audience rules.** Who may see what is the site's, on
|
||||
[Who sees who is online](/docs/rust/who-sees-who-is-online/).
|
||||
- **No site URL.** The site reaches the sidecar, never the other way round.
|
||||
- **No protocol version.** It is compiled in, because a sidecar that could be configured to claim
|
||||
another protocol would defeat the check.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`sidecar/src/config.rs`](https://gitea.whitlocktech.com/RunicGateway/Rust-Link/src/branch/main/sidecar/src/config.rs)
|
||||
is the authority;
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§6.2 describes it.
|
||||
65
src/content/docs/docs/rust/the-live-map.mdx
Normal file
65
src/content/docs/docs/rust/the-live-map.mdx
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: The live map
|
||||
description: The Map tab on each server's page — where the picture comes from, what the layers show and to whom, and why positions are never stored.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
Each server's public page has a **Map** tab: this wipe's map, with the game's own grid, its
|
||||
monuments, and layers for everything that moves. It is drawn in the browser from two things the
|
||||
bridge answers live — the picture, and the positions.
|
||||
|
||||
## The picture
|
||||
|
||||
**From Rust+, when it is on.** A server with `app.port` set renders its map at boot and keeps the
|
||||
picture for the Rust+ companion app; the bridge reads that picture and nothing else of Rust+'s, and
|
||||
the site fetches it once per map and keeps it. **Admin → Rust visibility** says, per server, where
|
||||
the picture came from, its size and when it was fetched, with **Fetch again**.
|
||||
|
||||
**Without Rust+, an admin asks for one**, with **Render now** on the same screen. The game can
|
||||
render the map on request, but it holds the server's main thread for several seconds while it
|
||||
does, so it never happens on its own. The
|
||||
rendered picture is kept on the game host and served again after a restart on the same map; a new
|
||||
map makes it stale.
|
||||
|
||||
The picture says nothing about who plays — it is drawn from the map's seed — so it is always
|
||||
public. Monument labels follow the in-game map's own; some are hidden by default, and the
|
||||
visibility screen says how many.
|
||||
|
||||
## The layers
|
||||
|
||||
Every layer has its own audience, set on
|
||||
[Who sees who is online](/docs/rust/who-sees-who-is-online/#the-live-map):
|
||||
|
||||
| Layer | What moves on it |
|
||||
|---|---|
|
||||
| **Monuments & world events** | Cargo, patrol and Chinook helicopters, Bradley, supply drops, locked crates — with how long a hack has left — and RunicNPC bosses placed outside an event |
|
||||
| **Site events** | The zones, crates and NPCs this site's events placed, while they run |
|
||||
| **Players** | Connected players, then offline sleepers, by name |
|
||||
| **Bases** | Tool cupboards and vending machines, **as positions only** — no owner, no authorised list, no shop name |
|
||||
| **PvP zones** | Dynamic PVP's zones, where they are now |
|
||||
| **Raid bases** | RaidableBases' live bases, their difficulty, when they despawn, and which an event spawned |
|
||||
|
||||
The players layer can never show more than *who is online* does. A linked player always sees
|
||||
themselves and their online clan mates, unless an operator turns that off.
|
||||
|
||||
<Aside type="note" title="Positions are never stored">
|
||||
The picture passes through the sidecar, and positions are never written to its database or the
|
||||
site's: the site holds the game's latest answer in memory for a few seconds, so many viewers cost
|
||||
one ask, and that is all. There is no record of where anybody was to leak later.
|
||||
</Aside>
|
||||
|
||||
## Bounds, and the console
|
||||
|
||||
Two keys in [`RunicGateway.json`](/docs/rust/runicgateway-json/), **`MapMaxSleepers`** and
|
||||
**`MapMaxBases`**, cap how many sleepers and bases one answer carries. Over a bound the layer is cut
|
||||
short and says so; a read has nobody to refuse, so it is trimmed rather than refused.
|
||||
|
||||
`rg.map` at the server console prints the map's key, where the picture came from, its size, whether
|
||||
a render is running, the game's grid, the size of each layer, and how many map asks there have
|
||||
been, with the last and the slowest.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§17 is the map on the wire, and §20.7 and §20.12 add its two optional layers.
|
||||
60
src/content/docs/docs/rust/troubleshooting.mdx
Normal file
60
src/content/docs/docs/rust/troubleshooting.mdx
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
description: Rust-side symptoms, what each one means, and where to look — from an install that stops to a site that shows nothing.
|
||||
---
|
||||
|
||||
Start with [Verify the bridge](/docs/rust/verify-the-bridge/): its four checks find which link is
|
||||
broken. The site-wide symptoms — a `409`, a `401` — are on core's
|
||||
[Troubleshooting](/docs/administration/troubleshooting/) page. This page is the Rust side.
|
||||
|
||||
## Installing
|
||||
|
||||
| Symptom | Cause, and the fix |
|
||||
|---|---|
|
||||
| The install stops: *already names this server "x"* | The plugin's config names another server. Use that id — or, if the server really is new to the site, remove the config file |
|
||||
| The install stops: *has both Oxide and Carbon* | A tree mid-migration. Remove the framework you are leaving |
|
||||
| The install stops: *has neither* | The server has never started with a framework. Install Oxide or Carbon and start it once |
|
||||
| `doctor`: *not the file that was deployed* | The plugin was edited by hand. `update --game rust` puts the released one back |
|
||||
| `doctor`: RunicNPC missing | It is required. Reinstall or update; and check Kits is installed, because RunicNPC will not load without it |
|
||||
| The egg's console prints a new token after every boot | A sidecar older than the first Rust-Link release does this. Reinstall the server |
|
||||
|
||||
## Connecting
|
||||
|
||||
| Symptom | Cause, and the fix |
|
||||
|---|---|
|
||||
| The site says the sidecar is unreachable | The site cannot reach the URL: a loopback bind with the site elsewhere, a firewall — or, on the egg, a sidecar port that is not one of the server's allocations |
|
||||
| **Test** says `protocol-mismatch` | The site's Rust module and the bridge speak different protocols. Update whichever is behind |
|
||||
| `doctor`: *plugin connected — no, and the server is running* | The plugin did not compile, or cannot reach its sidecar. The server console names a compile error; the plugin's `Port` must equal the sidecar's game port |
|
||||
| The sidecar logs *refusing a plugin that names another server* | Two servers dial one sidecar: a plugin's `Port` is another server's game port. Correct that plugin's `Port` and reload it. Nothing it sent was stored |
|
||||
| `/health` shows `untyped_frames` above zero | The plugin and the sidecar are on different protocols. Move both to one bundle |
|
||||
| The server is listed but reads stale | It reported once and has not since. Either the site's poll is failing (the site's log) or the sidecar stopped (its own) |
|
||||
|
||||
## Features
|
||||
|
||||
| Symptom | Cause, and the fix |
|
||||
|---|---|
|
||||
| Events refuse every step on a server | `EventsEnabled` is off in its [`RunicGateway.json`](/docs/rust/runicgateway-json/). It ships off |
|
||||
| A step is refused as over a bound | The server's own bound in `RunicGateway.json` is lower than the step asks for. Steps are refused, never trimmed |
|
||||
| A permission the site granted does nothing | *not registered here*: no loaded plugin registers that name, so the framework ignores it. Load the plugin. `rg.perms` says what the last sync did |
|
||||
| A group membership has not landed | The server has never seen that player. It lands on their first connection |
|
||||
| An event zone's flags do not hold | The ZoneManager helper is missing. Reinstall, or place `RunicGatewayZones.cs` by hand |
|
||||
| A config save rolled back | The plugin did not come back on the new file. The site shows the log line that says why |
|
||||
| RaidableBases purchases and raids stop working after a config change | It was reloaded on a running server. Restart the server — see [Optional plugins](/docs/rust/optional-plugins/#raidablebases) |
|
||||
| *Place a guarded crate* is refused | NpcSpawn is missing — see [Optional plugins](/docs/rust/optional-plugins/#guarded-crate) |
|
||||
| Chat titles show on the site but not in game | BetterChat is not loaded on that server |
|
||||
| The map tab has no picture | The server runs without Rust+. Ask for a render from Admin → Rust visibility |
|
||||
| No names in the feed or on the Online tab | That is the default. [Who sees who is online](/docs/rust/who-sees-who-is-online/) widens it |
|
||||
|
||||
## What to send when asking for help
|
||||
|
||||
The output of `rg.link` and `rg.hooks` at the server console, `/health` from the game host, and,
|
||||
where the installer was used, `doctor --game rust`. `rg.hooks` lists which of the plugin's hooks
|
||||
have fired: both frameworks bind hooks by name with no warning when a name matches nothing, so a
|
||||
hook Rust renamed on a wipe day shows up there as silence.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/INSTALL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INSTALL.md#troubleshooting)
|
||||
and
|
||||
[`rust-link/INTEGRATION.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INTEGRATION.md)
|
||||
§3 hold the full tables.
|
||||
101
src/content/docs/docs/rust/verify-the-bridge.mdx
Normal file
101
src/content/docs/docs/rust/verify-the-bridge.mdx
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: Verify the bridge
|
||||
description: The four links between a Rust server and the site, checked in order — the plugin's log, rg.link, the sidecar's /health, and Admin → Rust servers.
|
||||
---
|
||||
|
||||
import { Aside, Code } from '@astrojs/starlight/components';
|
||||
import platform from '../../../../data/platform.json';
|
||||
|
||||
export const P = platform.games.rust.protocol;
|
||||
|
||||
A copied file is not a working bridge. Check the four links in the order data travels, the
|
||||
way [Verify the whole stack](/docs/getting-started/verify-the-whole-stack/) describes for
|
||||
every game; this page has Rust's commands. The first link that fails is the one to fix.
|
||||
|
||||
## 1. The bridge plugin loaded
|
||||
|
||||
Both frameworks compile and load `RunicGateway.cs` the moment it lands. Oxide logs to
|
||||
`oxide/logs/`; Carbon to `carbon/logs/Carbon.Core.log`. A good load reads:
|
||||
|
||||
<Code code={`RunicGateway was compiled successfully
|
||||
[Runic Gateway] protocol ${P}, serverId 'main', sidecar 127.0.0.1:7799
|
||||
[Runic Gateway] connected to 127.0.0.1:7799`} />
|
||||
|
||||
The second line is worth reading closely: it is the protocol this plugin speaks, the id the
|
||||
site will file everything under, and the address it dials. A wrong `ServerId` or `Port` is
|
||||
visible right here.
|
||||
|
||||
If the third line is instead *cannot reach the sidecar: … — retrying quietly until it
|
||||
answers*, the plugin is fine and the sidecar is not there yet — or `Host` and `Port` in
|
||||
[`RunicGateway.json`](/docs/rust/runicgateway-json/) point somewhere else. It is printed once
|
||||
per load and then the plugin keeps trying.
|
||||
|
||||
## 2. The server reached the sidecar
|
||||
|
||||
At the server console (or over RCON):
|
||||
|
||||
<Code code={`rg.link
|
||||
protocol=${P} serverId=main connected=True depth=0 sent=412 dropped=0 received=38 connects=1 writeErrors=0 bootId=… wipeId=w-20261005T115051Z`} />
|
||||
|
||||
`connected=True` is the link. `dropped` above zero means the sidecar was away long enough for
|
||||
the plugin's queue to fill and shed its oldest lines — the game never waits for it.
|
||||
`wipeId=none` is a server that has never saved, which is normal for a few minutes after a
|
||||
fresh wipe.
|
||||
|
||||
## 3. The sidecar is healthy
|
||||
|
||||
On the game host. `/health` needs no token, so monitoring can use it:
|
||||
|
||||
<Code lang="bash" code={`curl -s http://127.0.0.1:8090/health
|
||||
{"status":"ok","protocol":${P},"plugin_connected":true,"database":"ok","uptime":"2h 5m","last_event":"…","untyped_frames":0}`} />
|
||||
|
||||
| Field | Working means |
|
||||
|---|---|
|
||||
| `plugin_connected` | `true`. **This is the one value that tells "files copied" from "the bridge works"** |
|
||||
| `status` | `ok`. `degraded` means the plugin is not connected or the database is unreadable |
|
||||
| `untyped_frames` | `0`. Anything else means the plugin and the sidecar speak different protocols — see below |
|
||||
|
||||
<Aside type="caution" title="untyped_frames above zero">
|
||||
The link between the plugin and the sidecar has no version handshake, so a mismatched pair is
|
||||
not refused at connect. The sidecar drops and counts every frame it cannot file instead. The
|
||||
symptom without this counter is a server plainly up, a sidecar plainly healthy, and a site
|
||||
showing nothing. Compare `rg.link`'s protocol with `/health`'s, and update whichever is
|
||||
behind — normally by moving both to one bundle.
|
||||
</Aside>
|
||||
|
||||
## 4. The site is ingesting
|
||||
|
||||
On **Admin → Rust servers**, **Test the sidecar** answers *The sidecar answered (protocol
|
||||
{P})*, and the row reads online with *last seen just now*. The public page at `/rust` lists the
|
||||
server, and its feed fills as things happen in the world.
|
||||
|
||||
Each failure the Test button can report is on
|
||||
[Connect the site](/docs/rust/connect-the-site/#test-it).
|
||||
|
||||
## Three things that look broken and are not
|
||||
|
||||
- **Stopping the sidecar does not disturb the game.** The plugin logs *sidecar link lost;
|
||||
reconnecting* and keeps retrying, into a bounded queue that drops its oldest lines rather
|
||||
than growing.
|
||||
- **A plugin started before its sidecar logs one line and goes quiet** until the sidecar
|
||||
answers.
|
||||
- **The site renders with every server off.** Lists, counts and last-reported times come from
|
||||
what the site stored, and the server reads offline.
|
||||
|
||||
## On the installer's hosts, one command
|
||||
|
||||
```bash
|
||||
runicgateway doctor --game rust
|
||||
```
|
||||
|
||||
checks the framework, every deployed file, RunicNPC, the required uMod plugins, the service
|
||||
and `/health` through to *plugin connected*, per server. It exits non-zero when a check
|
||||
failed, so a monitor can run it. On a Pterodactyl egg there is no shell; the console is the
|
||||
diagnosis.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/INTEGRATION.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/INTEGRATION.md)
|
||||
§3 and
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§5.1 cover diagnosing the link.
|
||||
85
src/content/docs/docs/rust/who-sees-who-is-online.mdx
Normal file
85
src/content/docs/docs/rust/who-sees-who-is-online.mdx
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Who sees who is online
|
||||
description: Admin → Rust visibility — nothing names a player by default; where an operator widens that, for presence, clan rosters, news in game chat and each live-map layer.
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
**Nothing on the site names who is online unless you choose to show it.** That covers more than
|
||||
the Online tab: the joins, deaths and chat in each server's feed, and the leaderboard's *last
|
||||
seen*, all say that a named player was on at a given moment. **How many** players are online is
|
||||
always shown — a count is never a name.
|
||||
|
||||
**Admin → Rust visibility** (`/admin/rust/visibility`) is where an operator widens that, on
|
||||
purpose. Every setting has a fleet-wide default and a per-server override.
|
||||
|
||||
## The audiences
|
||||
|
||||
Each setting picks one of three audiences, narrowest last:
|
||||
|
||||
| Audience | Who |
|
||||
|---|---|
|
||||
| **Everyone** | Anybody, signed in or not |
|
||||
| **Signed-in members** | Anybody with a site account |
|
||||
| **Staff only** | Admins and moderators |
|
||||
|
||||
## Who is online
|
||||
|
||||
The default is **staff only**. It decides who sees a **named** player: the Online tab, and the
|
||||
feed's [presence kinds](/docs/rust/event-kinds/#presence) — joins and leaves, respawns, deaths,
|
||||
chat, the per-minute tally, and the death of a RunicNPC boss, which names its killer. Below it,
|
||||
the public feed carries only what names nobody: a wipe, a start, a shutdown, a world event.
|
||||
|
||||
## Clan rosters
|
||||
|
||||
Who is in each clan, on every server. The default is **the clan's members and staff**: players
|
||||
whose linked Rust account is in the clan, plus admins and moderators. Each clan's name, colour,
|
||||
score and member count are always public.
|
||||
|
||||
## News in game chat
|
||||
|
||||
Per server: **off**, **in chat**, or **as a popup**. When a news post is published, its title is
|
||||
said on every server switched on here. A server that is down at that moment is skipped rather
|
||||
than told late. A popup needs PopupNotifications on that server; one without it refuses the post
|
||||
and says why.
|
||||
|
||||
## The live map
|
||||
|
||||
The map's **picture** is always public: it is drawn from the map's seed and says nothing about
|
||||
who plays. Everything that moves on it is a **layer**, and each layer has its own audience:
|
||||
|
||||
| Layer | What it shows | Default |
|
||||
|---|---|---|
|
||||
| **Monuments & world events** | Cargo, helicopters, Bradley, supply drops, locked crates, and RunicNPC bosses placed outside an event | Everyone |
|
||||
| **Site events** | The zones, crates and NPCs this site's events placed, while they run | Everyone |
|
||||
| **Players** | Where everybody is, with names, and the sleepers | Staff only |
|
||||
| **Bases** | Tool cupboards and vending machines, as positions only | Staff only |
|
||||
| **PvP zones** | Dynamic PVP's own zones. Needs Dynamic PVP on the server | Everyone |
|
||||
| **Raid bases** | RaidableBases' live bases and when they despawn. Needs RaidableBases | Everyone |
|
||||
|
||||
**The players layer can never show more than *who is online* does**, whatever it is set to here.
|
||||
**You and your clan**, on by default, lets a linked player see themselves and their online clan
|
||||
mates whatever the players layer says.
|
||||
|
||||
Per server, this section also says how many monument labels the map has and how many are hidden,
|
||||
and where the map's picture came from, with **Fetch again**. See
|
||||
[The live map](/docs/rust/the-live-map/).
|
||||
|
||||
<Aside type="note" title="Why the default is the narrowest one">
|
||||
A feed that says a named player was on at 3 a.m. is a record of somebody's evenings. So nobody's
|
||||
presence is published until an operator decides it should be, and a new server follows the
|
||||
fleet's default rather than starting open.
|
||||
</Aside>
|
||||
|
||||
## What this does not cover
|
||||
|
||||
Some kinds never reach a public page at any setting: IP addresses, player reports, bans, who
|
||||
destroyed whose base, and permission changes are [staff-only kinds](/docs/rust/event-kinds/#staff).
|
||||
They are not on this screen because there is nothing to choose.
|
||||
|
||||
## Canonical documents
|
||||
|
||||
[`rust-link/PROTOCOL.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/rust-link/PROTOCOL.md)
|
||||
§8.5 sets out why the boundary is the module's, and
|
||||
[`modules/rust/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust/PLAN.md)
|
||||
§23 records the presence rule.
|
||||
@@ -3,7 +3,8 @@ title: Verify the bridge
|
||||
description: Four checks on a ServUO shard, one per link in the chain, that distinguish "files copied" from "the bridge works".
|
||||
---
|
||||
|
||||
import { Aside, Steps } from '@astrojs/starlight/components';
|
||||
import { Aside, Code, Steps } from '@astrojs/starlight/components';
|
||||
import platform from '../../../../data/platform.json';
|
||||
|
||||
A successful install is not a working bridge, and the failure is quiet in a specific way:
|
||||
**ServUO shells out to `dotnet build`, prints the output, ignores the exit code, and
|
||||
@@ -54,9 +55,7 @@ So verify each link in the chain, in order. Each check tells you which one to fi
|
||||
curl -s http://127.0.0.1:8080/health
|
||||
```
|
||||
|
||||
```json
|
||||
{"status":"ok","protocol":4,"plugin_connected":true,"database":"ok","uptime":"2m"}
|
||||
```
|
||||
<Code lang="json" code={`{"status":"ok","protocol":${platform.games.uo.protocol},"plugin_connected":true,"database":"ok","uptime":"2m"}`} />
|
||||
|
||||
**`plugin_connected: true` is the one that matters.** It is the only value in this whole
|
||||
sequence that distinguishes "files copied" from "the bridge works".
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"the same way. Which game a repository belongs to is said once, under `games` below."
|
||||
],
|
||||
"releases": {
|
||||
"installer": "v0.8.0",
|
||||
"installer": "v0.8.1",
|
||||
"Android-app": "v0.5.0",
|
||||
"link": "v2.3.0",
|
||||
"Module-uo": "v1.3.0",
|
||||
|
||||
@@ -230,6 +230,141 @@ export const bridgeCfg = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* rust-link's `sidecar.toml` — `Rust-Link` `sidecar/src/config.rs`, parsed from its serde
|
||||
* structs the same way as uo-link's above. Each section is one `struct XCfg`.
|
||||
*/
|
||||
export const rustSidecarConfig = {
|
||||
'game.bind': 'Loopback address the bridge plugin dials. Never move it off loopback',
|
||||
'game.server_id':
|
||||
"A cross-check against the id the plugin announces. A plugin naming another server is refused before anything it sent is stored",
|
||||
'web.bind': 'Address the website reaches the sidecar on',
|
||||
'web.auth_token': 'Shared secret the website must present. Generated and saved on first run if blank',
|
||||
'store.path': "The sidecar's own database. Relative paths resolve beside the config file",
|
||||
'store.retain_days': 'Days of raw event history the sidecar keeps. 0 keeps everything',
|
||||
};
|
||||
|
||||
/**
|
||||
* The environment variables rust-link reads, each overriding one key above. Read from the
|
||||
* `get("RUSTLINK_…")` calls in the same file. An empty variable is ignored, which is what
|
||||
* lets a panel declare one and leave it blank.
|
||||
*/
|
||||
export const rustSidecarEnv = {
|
||||
RUSTLINK_GAME_BIND: 'game.bind',
|
||||
RUSTLINK_SERVER_ID: 'game.server_id',
|
||||
RUSTLINK_WEB_BIND: 'web.bind',
|
||||
RUSTLINK_WEB_TOKEN: 'web.auth_token',
|
||||
RUSTLINK_DB_PATH: 'store.path',
|
||||
RUSTLINK_RETAIN_DAYS: 'store.retain_days',
|
||||
};
|
||||
|
||||
/**
|
||||
* The bridge plugin's own config — `Rust-Plugins` `overlay/oxide/plugins/RunicGateway.cs`,
|
||||
* the public fields of its `PluginConfig` class. The framework writes it as
|
||||
* `oxide/config/RunicGateway.json` or `carbon/configs/RunicGateway.json`.
|
||||
*
|
||||
* `readOnlyFromSite` marks the three keys the site's mod config editor shows but will not
|
||||
* write: changing them from the website would cut the link carrying the change, or strand
|
||||
* every row the site holds for this server.
|
||||
*/
|
||||
export const rustPluginConfig = {
|
||||
Connection: {
|
||||
Host: { why: 'Where the sidecar listens. Loopback is the trust boundary', readOnlyFromSite: true },
|
||||
Port: { why: "The sidecar's game port. Must equal its game.bind port", readOnlyFromSite: true },
|
||||
QueueCap: { why: 'Lines that may wait for a wedged sidecar before the oldest is dropped' },
|
||||
ServerId: {
|
||||
why: "This server's permanent identity on the website. Not the hostname, on purpose",
|
||||
readOnlyFromSite: true,
|
||||
},
|
||||
},
|
||||
Events: {
|
||||
EventsEnabled: {
|
||||
why: 'Whether the website may borrow values or change this world for events at all. Off by default',
|
||||
},
|
||||
EventsMaxCrates: { why: 'Crates one event step may place. Lowerable, never raisable past the ceiling' },
|
||||
EventsMaxNpcs: { why: 'NPCs one event step may place' },
|
||||
EventsMaxRecipients: { why: 'People one reward step may name' },
|
||||
EventsMaxTallies: { why: 'Participation tallies counting at once' },
|
||||
EventsMaxChat: { why: 'Longest line an event or a news post may say in chat' },
|
||||
EventsMaxWorldCalls: { why: 'World events one run may call: helicopters, cargo ships, airdrops' },
|
||||
},
|
||||
Map: {
|
||||
MapMaxSleepers: { why: 'Offline sleepers one live-map answer carries' },
|
||||
MapMaxBases: { why: 'Tool cupboards and vending machines one live-map answer carries' },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Every event kind the Rust bridge sends, through its current protocol, as `Module-Rust`
|
||||
* `server/catalogue.js` classifies them (PLAN.md §16, D71). The module is the side that
|
||||
* enforces the boundary, so it is what this list is checked against — the same reason the
|
||||
* UO ladder below is checked against the module and not against prose.
|
||||
*
|
||||
* - `public`: a signed-out visitor may see the kind. Some are filtered field by field
|
||||
* inside it — a name for the presence audience, a position for staff — and that is the
|
||||
* module's business, summarised on the page rather than restated here.
|
||||
* - `presence`: public kinds that say a NAMED player was on the server. Their audience is
|
||||
* an operator's choice on Admin → Rust visibility, staff only by default. A subset of
|
||||
* the module's public list, kept separate here because that is how a reader needs it.
|
||||
* - `staff`: admins and moderators, and nobody else.
|
||||
*/
|
||||
export const rustEventKinds = {
|
||||
public: {
|
||||
'server.wipe': 'The map was wiped',
|
||||
'server.initialized': 'The server came up',
|
||||
'server.shutdown': 'The server went down',
|
||||
'npc.boss.spawned': 'A RunicNPC boss appeared',
|
||||
'world.event': "Rust's own world events: cargo, helicopters, Bradley, airdrops, the vendor",
|
||||
'pvp.zone.created': 'Dynamic PVP opened a PvP zone',
|
||||
'dt.started': 'A Dangerous Treasures event opened',
|
||||
'dt.won': 'A Dangerous Treasures chest was won',
|
||||
'gc.started': 'A guarded crate landed',
|
||||
'gc.ended': 'A guarded crate was cleared',
|
||||
'raid.started': 'A RaidableBases base appeared',
|
||||
'raid.completed': 'A raid base was completed',
|
||||
'raid.purchased': 'A raid base was bought',
|
||||
'raid.locked': 'An admin locked a raid base to a player',
|
||||
},
|
||||
presence: {
|
||||
'player.connected': 'A player joined',
|
||||
'player.disconnected': 'A player left',
|
||||
'player.respawned': 'A player respawned',
|
||||
'player.death': 'A death — the kill feed',
|
||||
'player.chat': 'A line of in-game chat',
|
||||
'player.tally': "A minute's gathering, kills and title counts, summed rather than sent per swing",
|
||||
'npc.boss.died': 'A RunicNPC boss was killed, naming the killer',
|
||||
},
|
||||
staff: {
|
||||
'entity.destroyed': 'A building part was destroyed — it says where somebody lives',
|
||||
'player.reported': "One player's report about another",
|
||||
'player.banned': 'A ban, with the IP address',
|
||||
'player.unbanned': 'An unban, with the IP address',
|
||||
'player.login.attempt': 'A login attempt, with the IP address',
|
||||
'player.approved': 'A login approved, with the IP address',
|
||||
'account.link.requested': 'Somebody asked for a link code — never the code itself',
|
||||
'account.unlinked': 'A Steam account was unlinked in game',
|
||||
'perm.drift': 'A permission or group was changed in game, outside the site',
|
||||
'config.outcome': "How a config save's reload ended, with the log tail when it failed",
|
||||
'plugin.loaded': 'A plugin loaded, with the permissions it registers',
|
||||
'plugin.unloaded': 'A plugin unloaded',
|
||||
'lease.expired': 'A value an event borrowed was given back on its deadline',
|
||||
'world.expired': 'Something an event placed ended on its deadline',
|
||||
'clan.created': 'A clan was created',
|
||||
'clan.disbanded': 'A clan was disbanded',
|
||||
'clan.member.added': 'Somebody joined a clan',
|
||||
'clan.member.left': 'Somebody left a clan',
|
||||
'clan.member.kicked': 'Somebody was removed from a clan',
|
||||
'npc.died': 'A RunicNPC NPC died, naming everyone who hurt it',
|
||||
'npc.health': 'A RunicNPC NPC fell to a health threshold',
|
||||
'npc.placement.changed': 'A RunicNPC placement changed',
|
||||
'npc.boss.phase': "A boss changed phase — told in game only to the players near it",
|
||||
'pvp.zone.deleted': 'A Dynamic PVP zone closed',
|
||||
'dt.ended': 'A Dangerous Treasures event ended, won or not',
|
||||
'gc.failed': 'A guarded crate timed out with its guards standing',
|
||||
'raid.ended': 'A raid base despawned',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The five-rung visibility ladder, from `module-uo`'s `server/utils/shardVisibility.js`.
|
||||
*
|
||||
@@ -275,5 +410,7 @@ export const canonicalDocs = {
|
||||
'rust-link/INTEGRATION.md': 'Integrating with the sidecar',
|
||||
'rust-link/PROTOCOL.md': 'The wire protocol between the bridge plugin and the sidecar',
|
||||
'modules/rust/PLAN.md': 'The Rust module design of record',
|
||||
'modules/rust/OPERATING.md': "Running Oxide: uMod's operator pages, plus the notes on RaidableBases and Guarded Crate",
|
||||
'modules/rust/CARBON.md': 'Where Carbon differs from Oxide, and nowhere else',
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user