Merge pull request 'feat(docs): phase 15 — the Rust group, its reference pages checked against their sources' (#35) from feat/phase-15-rust into main
Reviewed-on: #35
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