Phase 1 (plugin side) of docs/ADMIN_CONTROLS.md: a staff write plane so the website can moderate the live shard. - BridgeAdmin.cs: inbound admin.kick, admin.ban (timed + indefinite), admin.unban, admin.broadcast. Each requires an `actor`, refuses targets at or above AdminAccessFloor (default CoOwner — Owner-only shield), replies admin.ok/admin.error with the reqId echoed, and emits an admin.audit (origin=web) broadcast. Attribution is web:<actor> in the console log and the ban BanDealer tag. Kicking enumerates NetState.Instances so a character-select session is caught too. - BridgeConfig/Bridge.cfg: AdminWriteEnabled (default OFF — opt-in), AdminAccessFloor, broadcast/reason length caps, ban duration clamp. - tools/stub_sidecar_admin.ps1: live smoke-test harness; *.log gitignored. Verified: compiles clean against ServUO (0 err/warn); live run on the seeded shard confirms all four verbs, the audit stream, timed-ban fields, and the Owner-floor refusal, with no exceptions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
95 lines
4.1 KiB
C#
95 lines
4.1 KiB
C#
using System;
|
|
|
|
namespace Server.Custom.Bridge
|
|
{
|
|
/// <summary>
|
|
/// Tunables from Config/Bridge.cfg. Key scope is the filename, so `Port=7788` there
|
|
/// reads as "Bridge.Port" here.
|
|
///
|
|
/// Loaded in Configure(), which ScriptCompiler invokes before World.Load.
|
|
/// </summary>
|
|
public static class BridgeConfig
|
|
{
|
|
public static string Host { get; private set; }
|
|
public static int Port { get; private set; }
|
|
public static int QueueCap { get; private set; }
|
|
|
|
public static int StatSweepSeconds { get; private set; }
|
|
public static int DecaySweepSeconds { get; private set; }
|
|
public static int EconomySweepSeconds { get; private set; }
|
|
|
|
public static string LinkUrl { get; private set; }
|
|
|
|
public static int TownCrierMaxLines { get; private set; }
|
|
public static int TownCrierMaxLineLength { get; private set; }
|
|
public static int TownCrierMaxActive { get; private set; }
|
|
public static int TownCrierMaxDurationSec { get; private set; }
|
|
|
|
public static bool AdminWriteEnabled { get; private set; }
|
|
public static AccessLevel AdminAccessFloor { get; private set; }
|
|
public static int AdminBroadcastMaxLength { get; private set; }
|
|
public static int AdminReasonMaxLength { get; private set; }
|
|
public static int AdminBanMaxDurationSec { get; private set; }
|
|
|
|
public static bool Enabled { get; private set; }
|
|
|
|
public static void Configure()
|
|
{
|
|
Load();
|
|
}
|
|
|
|
/// <summary>Re-readable at runtime via `[bridge reload`.</summary>
|
|
public static void Load()
|
|
{
|
|
Enabled = Config.Get("Bridge.Enabled", true);
|
|
|
|
Host = Config.Get("Bridge.Host", "127.0.0.1");
|
|
Port = Config.Get("Bridge.Port", 7788);
|
|
QueueCap = Config.Get("Bridge.QueueCap", 10000);
|
|
|
|
StatSweepSeconds = Config.Get("Bridge.StatSweepSeconds", 30);
|
|
DecaySweepSeconds = Config.Get("Bridge.DecaySweepSeconds", 60);
|
|
EconomySweepSeconds = Config.Get("Bridge.EconomySweepSeconds", 300);
|
|
|
|
LinkUrl = Config.Get("Bridge.LinkUrl", "https://yoursite/link");
|
|
|
|
TownCrierMaxLines = Config.Get("Bridge.TownCrierMaxLines", 6);
|
|
TownCrierMaxLineLength = Config.Get("Bridge.TownCrierMaxLineLength", 200);
|
|
TownCrierMaxActive = Config.Get("Bridge.TownCrierMaxActive", 20);
|
|
TownCrierMaxDurationSec = Config.Get("Bridge.TownCrierMaxDurationSec", 86400);
|
|
|
|
AdminWriteEnabled = Config.Get("Bridge.AdminWriteEnabled", false);
|
|
AdminAccessFloor = ParseAccessLevel(Config.Get("Bridge.AdminAccessFloor", "CoOwner"), AccessLevel.CoOwner);
|
|
AdminBroadcastMaxLength = Config.Get("Bridge.AdminBroadcastMaxLength", 300);
|
|
AdminReasonMaxLength = Config.Get("Bridge.AdminReasonMaxLength", 400);
|
|
AdminBanMaxDurationSec = Config.Get("Bridge.AdminBanMaxDurationSec", 31536000);
|
|
|
|
if (QueueCap < 16)
|
|
QueueCap = 16;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses an AccessLevel name from config, case-insensitively, falling back to the given
|
|
/// default on anything unrecognized so a typo can never open the floor wider than intended.
|
|
/// </summary>
|
|
private static AccessLevel ParseAccessLevel(string value, AccessLevel fallback)
|
|
{
|
|
AccessLevel parsed;
|
|
if (!String.IsNullOrEmpty(value) && Enum.TryParse(value.Trim(), true, out parsed) &&
|
|
Enum.IsDefined(typeof(AccessLevel), parsed))
|
|
return parsed;
|
|
|
|
Console.WriteLine("[Bridge] unrecognized AdminAccessFloor '{0}', using {1}", value, fallback);
|
|
return fallback;
|
|
}
|
|
|
|
public static string Describe()
|
|
{
|
|
return String.Format(
|
|
"enabled={0} endpoint={1}:{2} queueCap={3} sweeps(stat={4}s decay={5}s econ={6}s) adminWrite={7}(floor={8})",
|
|
Enabled, Host, Port, QueueCap, StatSweepSeconds, DecaySweepSeconds, EconomySweepSeconds,
|
|
AdminWriteEnabled, AdminAccessFloor);
|
|
}
|
|
}
|
|
}
|