From f2f202ce7dd0759e61ba20dfd0e74639f759377c Mon Sep 17 00:00:00 2001 From: 4lve Date: Thu, 2 Jul 2026 18:15:32 +0200 Subject: [PATCH 1/5] Initial commands and permissions --- src/content/docs/configuration/overview.md | 1 + src/content/docs/configuration/permissions.md | 278 ++++++++++++++++++ src/content/docs/development/commands.md | 254 ++++++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 src/content/docs/configuration/permissions.md create mode 100644 src/content/docs/development/commands.md diff --git a/src/content/docs/configuration/overview.md b/src/content/docs/configuration/overview.md index a8c369a..dcb5a80 100644 --- a/src/content/docs/configuration/overview.md +++ b/src/content/docs/configuration/overview.md @@ -12,6 +12,7 @@ Configure your SteelMC server with custom settings and features. ## Available Guides - [**Server Links**](../server-links) - Configure server links displayed in the multiplayer menu +- [**Permission Configuration**](../permissions) - Configure groups, command permissions, operators and runtime permission edits ## Reference Documentation diff --git a/src/content/docs/configuration/permissions.md b/src/content/docs/configuration/permissions.md new file mode 100644 index 0000000..6f567d0 --- /dev/null +++ b/src/content/docs/configuration/permissions.md @@ -0,0 +1,278 @@ +--- +title: Permission Configuration +description: Configure command permissions, groups, player overrides, and permission metadata in SteelMC. +sidebar: + order: 4 +--- + +SteelMC permissions are configured through `config/groups.toml` and can also be edited at runtime with `/steelperms` or its alias `/sp`. + +Permissions control command access, command suggestions, and the command tree sent to each client. The same system also supports contextual rules and typed metadata values for server features and plugins. + +## Files + +Steel uses two permission files: + +| File | Purpose | +| ---- | ------- | +| `config/groups.toml` | Server-wide permission groups and default groups | +| `/global/player_permissions.toml` | Per-player assigned groups, direct overrides, and metadata | + +Edit `groups.toml` for normal server policy. The player permissions file is under the configured world `save_path`, which defaults to `saves`. It is managed by Steel and `/steelperms`; manual edits should only be needed for recovery or bulk migration while the server is offline. + +## Default Groups + +A new server creates this basic group configuration: + +```toml +default_groups = ["default"] + +[groups.default] +priority = 0 +allow = [] +deny = [] +metadata = [] + +[groups.op] +priority = 0 +allow = ["*"] +deny = [] +metadata = [] +``` + +Every player receives all groups listed in `default_groups`. The `op` group is required, grants `*` by default, and is what `/op` assigns. + +:::caution +Do not put `op` in `default_groups` unless every player should have every permission. +::: + +## Permission Keys + +Permission keys are dotted lowercase strings: + +```text +minecraft.command.give +steel.command.fly +plugin.region.build +``` + +Segments can contain lowercase ASCII letters, digits, `_`, and `-`. Empty segments and uppercase letters are invalid. + +Wildcards are allowed only as the final segment: + +| Key | Meaning | +| --- | ------- | +| `*` | Matches every permission | +| `minecraft.command.*` | Matches descendants such as `minecraft.command.give` | +| `minecraft.command.give` | Matches only that exact key | + +`minecraft.command.*` matches `minecraft.command.give`, but it does not match `minecraft.command` itself. + +## Command Permissions + +Most built-in commands automatically use this permission shape: + +```text +.command. +``` + +Examples: + +| Command | Permission | +| ------- | ---------- | +| `/give` | `minecraft.command.give` | +| `/fly` | `steel.command.fly` | +| `/tp` and `/teleport` | `minecraft.command.teleport` | +| `/xp` and `/experience` | `minecraft.command.experience` | +| `/steelperms` and `/sp` | `steel.command.steelperms` | + +Public commands, such as `/list`, do not require a permission. + +Some commands expose narrower subcommand or value permissions. For example, `/tick freeze` can be granted with either `minecraft.command.tick` or `minecraft.command.tick.freeze`. + +`/gamemode` has value-specific permissions: + +| Action | Permission | +| ------ | ---------- | +| Any gamemode change | `minecraft.command.gamemode` | +| Survival only | `minecraft.command.gamemode.survival` | +| Creative only | `minecraft.command.gamemode.creative` | +| Adventure only | `minecraft.command.gamemode.adventure` | +| Spectator only | `minecraft.command.gamemode.spectator` | + +A broader permission grants the child action, but a more specific deny can override it. For example, allow `minecraft.command.gamemode` and deny `minecraft.command.gamemode.creative` to allow all gamemode changes except creative. + +## Group Configuration + +Groups are tables under `[groups.]`. + +```toml +[groups.moderator] +priority = 10 +allow = [ + "minecraft.command.kick", + "minecraft.command.ban", + "minecraft.command.gamemode.spectator", +] +deny = [ + "minecraft.command.stop", +] +metadata = [] +``` + +Group names must be valid permission segments: lowercase letters, digits, `_`, and `-`. + +### Contextual Rules + +Permission and metadata rules can include context selectors: + +```toml +[groups.builder] +priority = 5 +allow = [ + "plugin.region.build{world=lobby:spawn,plugin:region=market}", + "minecraft.command.gamemode{domain=lobby}", +] +deny = [ + "minecraft.command.gamemode.creative{world=lobby:spawn}", +] +metadata = [ + { key = "plugin:homes{domain=lobby}", value = 3 }, +] +``` + +Built-in context keys: + +| Context | Example | Meaning | +| ------- | ------- | ------- | +| `domain` | `domain=lobby` | Applies only inside one [domain](../../reference/terminology#domain) | +| `world` | `world=lobby:spawn` | Applies only inside one loaded [world](../../reference/terminology#world) | + +Custom contexts can be provided by plugins or server subsystems. Unqualified keys such as `region=spawn` are valid, but namespaced keys such as `plugin:region=spawn` are preferred for plugin-owned contexts. + +Multiple selectors are combined with AND: + +```text +plugin.region.build{world=lobby:spawn,plugin:region=market} +``` + +Context values cannot be empty and cannot contain whitespace, `{`, `}`, `,`, or `=`. + +## Metadata + +Metadata is typed data resolved by the same group and context model as permissions. Values can be booleans, integers, or strings. + +```toml +[groups.vip] +priority = 20 +allow = [] +deny = [] +metadata = [ + { key = "plugin:homes", value = 10 }, + { key = "plugin:chat/color", value = "gold" }, + { key = "plugin:can_fly", value = true }, +] +``` + +Metadata keys must be namespaced identifiers such as `plugin:homes` or `plugin:chat/color`. + +## Conflict Resolution + +Unset permissions are denied. When multiple rules match, Steel chooses the winning rule in this order: + +1. More specific permission key wins. +2. More specific context wins. +3. Direct player overrides beat group rules when specificity ties. +4. Higher group priority wins when group rules tie. +5. Deny wins when everything else ties. + +This means a specific deny can override a broad allow: + +```toml +[groups.staff] +priority = 10 +allow = ["minecraft.command.*"] +deny = ["minecraft.command.stop"] +metadata = [] +``` + +The player can use most Minecraft commands, but not `/stop`. + +## Runtime Commands + +Use `/steelperms` or `/sp` to inspect and edit permissions while the server is running. The root permission is `steel.command.steelperms`. + +Useful commands: + +```text +/steelperms user info +/steelperms user check +/steelperms user allow +/steelperms user deny +/steelperms user unset + +/steelperms user group add +/steelperms user group remove + +/steelperms group create +/steelperms group info +/steelperms group allow +/steelperms group deny +/steelperms group unset +/steelperms group priority + +/steelperms groups list +/steelperms groups default add +/steelperms groups default remove +``` + +Metadata can also be edited: + +```text +/steelperms user metadata set int +/steelperms user metadata set bool +/steelperms user metadata set string +/steelperms user metadata check +/steelperms user metadata unset + +/steelperms group metadata set int +/steelperms group metadata set bool +/steelperms group metadata set string +/steelperms group metadata unset +``` + +`` is a permission key plus an optional context selector, such as: + +```text +minecraft.command.gamemode{domain=lobby} +``` + +`` uses the same context selector syntax: + +```text +plugin:homes{world=survival:overworld} +``` + +Some operations that touch offline players or persist group file changes run in the background. The command sender receives the immediate result first, then a later message when background work completes. + +## Operators + +`/op ` and `/deop ` are backed by the permission system: + +- `/op` adds the `op` group. +- `/deop` removes the `op` group. +- The default `op` group grants `*`. +- The `op` group is required and cannot be deleted with `/steelperms`. + +Targets can be online players, known offline players, or profile names the server can resolve. + +## Client Command Tree + +Players only receive command nodes they are allowed to use. This affects: + +- visible commands in the client command tree +- tab completion +- server-side command suggestions +- the client gamemode switcher + +Console and RCON bypass permission checks. diff --git a/src/content/docs/development/commands.md b/src/content/docs/development/commands.md new file mode 100644 index 0000000..f2f8845 --- /dev/null +++ b/src/content/docs/development/commands.md @@ -0,0 +1,254 @@ +--- +title: Command Registration +description: How SteelMC commands are built, registered, and connected to permissions. +--- + +SteelMC commands are built as a graph of literal and argument nodes. The command registration step attaches root permissions, resolves subcommand permissions, registers aliases, validates ambiguity, and publishes command permissions for suggestions. + +## Command Modules + +Built-in commands live in `steel-core/src/command/commands/*.rs`. + +A built-in command module is registered automatically when it exposes both: + +```rust +pub(crate) const REGISTRATION: CommandRegistrationSpec = CommandRegistrationSpec::minecraft(); + +pub(crate) fn command() -> CommandNodeBuilder { + literal("example") +} +``` + +The build script scans the command directory and generates the built-in command registration list. Modules without both items are ignored or rejected by the build script. + +Use the registration namespace that owns the command: + +```rust +pub(crate) const REGISTRATION: CommandRegistrationSpec = CommandRegistrationSpec::minecraft(); +pub(crate) const REGISTRATION: CommandRegistrationSpec = CommandRegistrationSpec::steel(); +``` + +## Building a Command Tree + +Use `literal(...)` for fixed command words and `argument(...)` for parsed values: + +```rust +use crate::command::graph::{ + CommandNodeBuilder, CommandResult, ParsedArguments, argument, literal, +}; +use crate::command::parsers::PlayerParser; + +pub(crate) fn command() -> CommandNodeBuilder { + literal("example") + .then(literal("reload").executes(reload)) + .then( + literal("inspect") + .then(argument("target", PlayerParser::one()).executes(inspect)), + ) +} +``` + +Common builder methods: + +| Method | Purpose | +| ------ | ------- | +| `.then(child)` | Add one child node | +| `.then_all(children)` | Add several child nodes | +| `.executes(handler)` | Mark a node executable | +| `.requires(requirement)` | Add a source or permission requirement | +| `.requires_permission(key)` | Require one permission key | +| `.requires_permission_expr(expr)` | Require a composed permission expression | +| `.redirects(...)` | Redirect command execution | +| `.redirects_returning(...)` | Redirect and return the redirected result | +| `.forks(...)` | Fork execution to multiple command contexts | + +Executors receive `&mut CommandContext` and `&ParsedArguments`, and return `Result`. + +## Parsed Arguments + +Argument parsers store typed values in `ParsedArguments`. Read values by the argument name used in the tree: + +```rust +fn inspect( + context: &mut CommandContext, + arguments: &ParsedArguments, +) -> Result { + let targets = resolve_required_player_targets(arguments, "target", context)?; + + Ok(CommandResult::from_usize_success_count(targets.len())) +} +``` + +For simple typed arguments, use `arguments.get::("name")` and convert parser errors with the local command helper: + +```rust +let rate = arguments + .get::("rate") + .map_err(super::invalid_parsed_argument)?; +``` + +Parsers provide server parsing, client parser metadata, examples for ambiguity checks, and suggestions. The server parser can be stricter than the client metadata when the Minecraft client does not have a matching native parser. + +## Automatic Root Permissions + +Commands receive a root permission unless the registration is public: + +```text +.command. +``` + +Examples: + +| Registration | Root literal | Permission | +| ------------ | ------------ | ---------- | +| `CommandRegistrationSpec::minecraft()` | `give` | `minecraft.command.give` | +| `CommandRegistrationSpec::steel()` | `fly` | `steel.command.fly` | + +The root permission gates parsing, suggestions, and the command tree sent to players. Console and RCON pass permission requirements automatically. + +Use `.public()` for commands that should not require a permission: + +```rust +pub(crate) const REGISTRATION: CommandRegistrationSpec = + CommandRegistrationSpec::minecraft().public(); +``` + +## Aliases and Permission Bases + +Aliases register extra root literals for the same command graph: + +```rust +pub(crate) const REGISTRATION: CommandRegistrationSpec = + CommandRegistrationSpec::minecraft() + .permission_base("teleport") + .aliases(&["teleport"]); + +pub(crate) fn command() -> CommandNodeBuilder { + literal("tp") +} +``` + +This makes `/tp` and `/teleport` use `minecraft.command.teleport`. + +Use `.permission_base("name")` when the root literal should not become the permission segment. This is required for aliases like `/tp` where the canonical permission should be `teleport`. + +Alias names are validated as command literals. The permission base is validated as a permission segment. + +## Subcommand Permissions + +Use `.requires_subcommand_permission()` on a non-root literal when a subcommand can be granted independently from the root command: + +```rust +pub(crate) fn command() -> CommandNodeBuilder { + literal("tick") + .then(literal("query").executes(query_tick)) + .then( + literal("freeze") + .requires_subcommand_permission() + .executes(freeze_tick), + ) +} +``` + +For a Minecraft command rooted at `/tick`, this derives: + +```text +minecraft.command.tick.freeze +``` + +The user may hold either `minecraft.command.tick` or `minecraft.command.tick.freeze`. Holding only the child permission exposes the root command enough to reach that child. + +Use `.requires_additional_subcommand_permission()` when the user must hold both the root command permission and the derived child permission: + +```rust +literal("delete") + .requires_additional_subcommand_permission() + .executes(delete_group) +``` + +This is useful for sensitive actions inside a broader command family. + +Use `.permission_path_passthrough()` for syntax-only literals that should not appear in derived permission keys. + +## Dynamic Argument Permissions + +Some commands derive a permission segment from a parsed argument value. `/gamemode` is the current built-in example: + +```rust +pub(crate) fn command() -> CommandNodeBuilder { + literal("gamemode").then( + argument("gamemode", GameModeParser) + .requires_argument_permission::("gamemode") + .executes(set_own_game_mode), + ) +} +``` + +`GameType` implements `CommandPermissionArgument`, so these value permissions are available: + +```text +minecraft.command.gamemode.survival +minecraft.command.gamemode.creative +minecraft.command.gamemode.adventure +minecraft.command.gamemode.spectator +``` + +The root permission `minecraft.command.gamemode` grants all values, but a more specific value rule can override it. Suggestions are filtered the same way, so a player with only `minecraft.command.gamemode.survival` only sees `survival`. + +When adding a dynamic permission argument: + +1. Implement `CommandPermissionArgument` for the parsed value type. +2. Convert the parsed value into one `PermissionSegment`. +3. Return all possible segments from `catalog_permission_segments()`. +4. Use `.requires_argument_permission::("argument_name")` after the matching argument node. + +The catalog must be finite so Steel can publish child permissions for suggestions and management tools. + +## Permission Expressions + +Command requirements can use `PermissionExpr` directly for compound checks: + +```rust +let root = PermissionKey::parse("minecraft.command.gamemode")?; +let creative = PermissionKey::parse("minecraft.command.gamemode.creative")?; + +let permission = PermissionExpr::scoped_key(root, creative); +``` + +`PermissionExpr` supports: + +| Expression | Meaning | +| ---------- | ------- | +| `PermissionExpr::key(key)` | Require one key | +| `PermissionExpr::scoped_key(parent, key)` | Parent grants the child unless a more specific child rule overrides it | +| `left & right` | Require all child expressions | +| `left | right` | Require at least one child expression | + +This expression model is for command requirements. Group config and `/steelperms` rule strings use a single key plus optional context selector, such as `minecraft.command.gamemode{domain=lobby}`. + +## Registration Validation + +Normal command registration resolves derived and dynamic permission markers. Calling `.build()` directly on a tree that still contains those markers fails. + +Registration also validates the command graph before committing it: + +- root commands must be literal nodes +- aliases must be valid command literal names +- permission bases must be valid permission segments +- terminal arguments cannot have children or redirects +- ambiguous child parsers are rejected +- failed alias registration does not leave the primary root registered + +## Client Command Tree and Suggestions + +`CommandDispatcher` builds a filtered command tree for each player. Nodes whose requirements fail are omitted, and permission-backed nodes are marked with the protocol restricted flag. + +The same graph and requirements are used for parsing and suggestions. This means command permissions affect: + +- whether a player can run the command +- which command nodes the client sees +- tab completion +- server-side suggestions +- value suggestions for dynamic permissions + +The permission context is captured from the command source at command start. Later `/execute` changes to world, entity, or position do not change the permission authority for that command. From 7d5072ffb973ca8384095ba6c9066984eb2fc7c3 Mon Sep 17 00:00:00 2001 From: 4lve Date: Sat, 11 Jul 2026 19:05:13 +0200 Subject: [PATCH 2/5] updates --- src/content/docs/configuration/permissions.md | 123 +++++--- src/content/docs/development/commands.md | 271 +++++++----------- 2 files changed, 183 insertions(+), 211 deletions(-) diff --git a/src/content/docs/configuration/permissions.md b/src/content/docs/configuration/permissions.md index 6f567d0..6f7cfb7 100644 --- a/src/content/docs/configuration/permissions.md +++ b/src/content/docs/configuration/permissions.md @@ -5,7 +5,7 @@ sidebar: order: 4 --- -SteelMC permissions are configured through `config/groups.toml` and can also be edited at runtime with `/steelperms` or its alias `/sp`. +SteelMC permissions are configured through `config/groups.toml` and can also be edited at runtime with `/perms`. Permissions control command access, command suggestions, and the command tree sent to each client. The same system also supports contextual rules and typed metadata values for server features and plugins. @@ -18,7 +18,7 @@ Steel uses two permission files: | `config/groups.toml` | Server-wide permission groups and default groups | | `/global/player_permissions.toml` | Per-player assigned groups, direct overrides, and metadata | -Edit `groups.toml` for normal server policy. The player permissions file is under the configured world `save_path`, which defaults to `saves`. It is managed by Steel and `/steelperms`; manual edits should only be needed for recovery or bulk migration while the server is offline. +Edit `groups.toml` for normal server policy. The player permissions file is under the configured world `save_path`, which defaults to `saves`. It is managed by Steel and `/perms`; manual edits should only be needed for recovery or bulk migration while the server is offline. ## Default Groups @@ -29,12 +29,14 @@ default_groups = ["default"] [groups.default] priority = 0 +inherits = [] allow = [] deny = [] metadata = [] [groups.op] priority = 0 +inherits = [] allow = ["*"] deny = [] metadata = [] @@ -84,9 +86,9 @@ Examples: | `/fly` | `steel.command.fly` | | `/tp` and `/teleport` | `minecraft.command.teleport` | | `/xp` and `/experience` | `minecraft.command.experience` | -| `/steelperms` and `/sp` | `steel.command.steelperms` | +| `/perms` | `steel.command.perms` | -Public commands, such as `/list`, do not require a permission. +Default-access commands, such as `/list`, work when their permission is unset but can still be disabled with an explicit deny. Some commands expose narrower subcommand or value permissions. For example, `/tick freeze` can be granted with either `minecraft.command.tick` or `minecraft.command.tick.freeze`. @@ -109,9 +111,9 @@ Groups are tables under `[groups.]`. ```toml [groups.moderator] priority = 10 +inherits = [] allow = [ - "minecraft.command.kick", - "minecraft.command.ban", + "minecraft.command.teleport", "minecraft.command.gamemode.spectator", ] deny = [ @@ -122,6 +124,21 @@ metadata = [] Group names must be valid permission segments: lowercase letters, digits, `_`, and `-`. +### Group Inheritance + +Groups can inherit the permissions and metadata of other groups: + +```toml +[groups.moderator] +priority = 10 +inherits = ["helper"] +allow = ["minecraft.command.teleport"] +deny = [] +metadata = [] +``` + +Inheritance is transitive, and each inherited group contributes at most once. Cycles and references to missing groups make the configuration invalid. An inherited rule keeps the priority of the group where it was defined; it does not take on the child group's priority. + ### Contextual Rules Permission and metadata rules can include context selectors: @@ -129,6 +146,7 @@ Permission and metadata rules can include context selectors: ```toml [groups.builder] priority = 5 +inherits = [] allow = [ "plugin.region.build{world=lobby:spawn,plugin:region=market}", "minecraft.command.gamemode{domain=lobby}", @@ -156,7 +174,11 @@ Multiple selectors are combined with AND: plugin.region.build{world=lobby:spawn,plugin:region=market} ``` -Context values cannot be empty and cannot contain whitespace, `{`, `}`, `,`, or `=`. +Each context key can appear only once in an expression. A world automatically implies the domain in its namespace, so `world=lobby:spawn` also matches rules scoped to `domain=lobby`. If both are written, the domain must match the world's namespace; Steel removes the redundant domain when it stores the expression. Conflicting pairs such as `{domain=survival,world=lobby:spawn}` are invalid. + +Context values cannot be empty and cannot contain whitespace, `{`, `}`, `,`, or `=`. Domain names use Minecraft identifier-namespace syntax, and worlds must be written as `:`. + +Context specificity is additive. Global rules are least specific, a domain or custom selector adds one level, and a world adds two because it identifies both a domain and a loaded world. A rule with several matching selectors is therefore more specific than a rule with only one of them. ## Metadata @@ -165,6 +187,7 @@ Metadata is typed data resolved by the same group and context model as permissio ```toml [groups.vip] priority = 20 +inherits = [] allow = [] deny = [] metadata = [ @@ -176,6 +199,15 @@ metadata = [ Metadata keys must be namespaced identifiers such as `plugin:homes` or `plugin:chat/color`. +Metadata resolution considers only entries with the requested exact metadata key. Among matching entries, Steel chooses: + +1. The most specific context. +2. A direct player value over a group value when specificity ties. +3. The higher group priority when group values tie. +4. The last entry in the effective metadata set when everything else ties. + +Unlike permissions, metadata has no allow/deny state and metadata keys do not use wildcard matching. Setting a value replaces only the entry with the same metadata key and exact context; unsetting it likewise removes only that exact entry. A global value and a contextual value for the same key can coexist. + ## Conflict Resolution Unset permissions are denied. When multiple rules match, Steel chooses the winning rule in this order: @@ -191,6 +223,7 @@ This means a specific deny can override a broad allow: ```toml [groups.staff] priority = 10 +inherits = [] allow = ["minecraft.command.*"] deny = ["minecraft.command.stop"] metadata = [] @@ -200,47 +233,53 @@ The player can use most Minecraft commands, but not `/stop`. ## Runtime Commands -Use `/steelperms` or `/sp` to inspect and edit permissions while the server is running. The root permission is `steel.command.steelperms`. +Use `/perms` to inspect and edit permissions while the server is running. The root permission is `steel.command.perms`; there are no `/steelperms` or `/sp` aliases. Useful commands: ```text -/steelperms user info -/steelperms user check -/steelperms user allow -/steelperms user deny -/steelperms user unset - -/steelperms user group add -/steelperms user group remove - -/steelperms group create -/steelperms group info -/steelperms group allow -/steelperms group deny -/steelperms group unset -/steelperms group priority - -/steelperms groups list -/steelperms groups default add -/steelperms groups default remove +/perms user info +/perms user check +/perms user allow +/perms user deny +/perms user unset + +/perms user group add +/perms user group remove + +/perms group create +/perms group info +/perms group delete +/perms group allow +/perms group deny +/perms group unset +/perms group priority +/perms group inherit list +/perms group inherit add +/perms group inherit remove + +/perms groups list +/perms groups default add +/perms groups default remove ``` Metadata can also be edited: ```text -/steelperms user metadata set int -/steelperms user metadata set bool -/steelperms user metadata set string -/steelperms user metadata check -/steelperms user metadata unset - -/steelperms group metadata set int -/steelperms group metadata set bool -/steelperms group metadata set string -/steelperms group metadata unset +/perms user metadata set int +/perms user metadata set bool +/perms user metadata set string +/perms user metadata check +/perms user metadata unset + +/perms group metadata set int +/perms group metadata set bool +/perms group metadata set string +/perms group metadata unset ``` +`/perms user ... metadata check` resolves the effective value at the context written in `` and reports the winning source. Group metadata has no separate `check` command; use group info to inspect configured entries. + `` is a permission key plus an optional context selector, such as: ```text @@ -253,7 +292,13 @@ minecraft.command.gamemode{domain=lobby} plugin:homes{world=survival:overworld} ``` -Some operations that touch offline players or persist group file changes run in the background. The command sender receives the immediate result first, then a later message when background work completes. +`/perms` operations can resolve offline profiles and persist changes without blocking the server tick. Command execution remains suspended until the operation finishes, then the sender receives its result and feedback. + +### Administration Permissions + +Each branch also has a granular command permission, such as `steel.command.perms.user.info`, `steel.command.perms.user.allow`, or `steel.command.perms.group.inherit.add`. Holding `steel.command.perms` grants every branch unless a more specific branch is denied. + +Editing a rule additionally requires authority over its target permission. `steel.permission.manage.*` grants authority over all permission keys, while narrower keys such as `steel.permission.manage.minecraft.command.*` limit what the administrator can change. Group administration uses the corresponding `steel.permission.group.*` authority. Viewing metadata in user/group info and checking or editing metadata requires `steel.permission.metadata`; without it, metadata is omitted from info output. These non-command permissions are published for `/perms` autocomplete. ## Operators @@ -262,7 +307,7 @@ Some operations that touch offline players or persist group file changes run in - `/op` adds the `op` group. - `/deop` removes the `op` group. - The default `op` group grants `*`. -- The `op` group is required and cannot be deleted with `/steelperms`. +- The `op` group is required and cannot be deleted with `/perms`. Targets can be online players, known offline players, or profile names the server can resolve. diff --git a/src/content/docs/development/commands.md b/src/content/docs/development/commands.md index f2f8845..c0e36e6 100644 --- a/src/content/docs/development/commands.md +++ b/src/content/docs/development/commands.md @@ -3,252 +3,179 @@ title: Command Registration description: How SteelMC commands are built, registered, and connected to permissions. --- -SteelMC commands are built as a graph of literal and argument nodes. The command registration step attaches root permissions, resolves subcommand permissions, registers aliases, validates ambiguity, and publishes command permissions for suggestions. +SteelMC uses a Brigadier-style graph of literal and argument nodes. A separate `CommandRegistration` gives each graph a stable namespaced identity, aliases, and its permission policy. ## Command Modules -Built-in commands live in `steel-core/src/command/commands/*.rs`. - -A built-in command module is registered automatically when it exposes both: +Built-in commands live in `steel-core/src/command/builtins/`. Each module exposes a registration function and builds its command graph separately: ```rust -pub(crate) const REGISTRATION: CommandRegistrationSpec = CommandRegistrationSpec::minecraft(); +use steel_utils::Identifier; + +use super::super::{ + brigadier::{CommandNodeBuilder, CommandSyntaxError}, + execution::{ + CommandSource, SteelCommandContext, SteelCommandRuntime, literal, + }, + registration::CommandRegistration, +}; -pub(crate) fn command() -> CommandNodeBuilder { - literal("example") +pub(super) fn registration() -> CommandRegistration { + CommandRegistration::new(Identifier::vanilla_static("example"), |_| command()) } -``` -The build script scans the command directory and generates the built-in command registration list. Modules without both items are ignored or rejected by the build script. - -Use the registration namespace that owns the command: +fn command() -> CommandNodeBuilder { + literal("example").executes(run) +} -```rust -pub(crate) const REGISTRATION: CommandRegistrationSpec = CommandRegistrationSpec::minecraft(); -pub(crate) const REGISTRATION: CommandRegistrationSpec = CommandRegistrationSpec::steel(); +fn run( + context: &SteelCommandContext, +) -> Result { + context.source().send_success(&"Example ran".into()); + Ok(1) +} ``` -## Building a Command Tree +Add the module and its `registration()` call to `steel-core/src/command/builtins/mod.rs`. Registration is explicit; the build script no longer discovers command modules. -Use `literal(...)` for fixed command words and `argument(...)` for parsed values: +The registration ID is authoritative. Its path must equal the root literal (`minecraft:example` has the root `example`), and its namespace owns the default permission. -```rust -use crate::command::graph::{ - CommandNodeBuilder, CommandResult, ParsedArguments, argument, literal, -}; -use crate::command::parsers::PlayerParser; +## Building a Command Graph + +Use `literal(...)` for fixed words and `argument(...)` for parsed values: -pub(crate) fn command() -> CommandNodeBuilder { +```rust +fn command() -> CommandNodeBuilder { literal("example") .then(literal("reload").executes(reload)) .then( - literal("inspect") - .then(argument("target", PlayerParser::one()).executes(inspect)), + literal("inspect").then( + argument("target", SteelArgumentType::players()) + .executes(inspect), + ), ) } ``` -Common builder methods: +The most common builder methods are: | Method | Purpose | -| ------ | ------- | -| `.then(child)` | Add one child node | -| `.then_all(children)` | Add several child nodes | -| `.executes(handler)` | Mark a node executable | -| `.requires(requirement)` | Add a source or permission requirement | -| `.requires_permission(key)` | Require one permission key | -| `.requires_permission_expr(expr)` | Require a composed permission expression | -| `.redirects(...)` | Redirect command execution | -| `.redirects_returning(...)` | Redirect and return the redirected result | -| `.forks(...)` | Fork execution to multiple command contexts | +| --- | --- | +| `.then(child)` | Adds a child node | +| `.executes(handler)` | Adds a synchronous executor | +| `.executes_suspended(handler)` | Adds an executor that can finish asynchronously | +| `.requires(requirement)` | Replaces the node requirement | +| `.also_requires(requirement)` | Combines another requirement with the existing one | +| `.redirects(target)` | Redirects parsing without changing the source | +| `.redirects_with_modifier(...)` | Redirects with a source transformation or fork | -Executors receive `&mut CommandContext` and `&ParsedArguments`, and return `Result`. +Executors receive `&SteelCommandContext` and return `Result`. The integer is the command result used by features such as `/execute store` and return-value propagation. -## Parsed Arguments +## Arguments -Argument parsers store typed values in `ParsedArguments`. Read values by the argument name used in the tree: +Built-in Minecraft-aware parsers are exposed through `SteelArgumentType`; primitive Brigadier parsers use `ArgumentType`: ```rust -fn inspect( - context: &mut CommandContext, - arguments: &ParsedArguments, -) -> Result { - let targets = resolve_required_player_targets(arguments, "target", context)?; - - Ok(CommandResult::from_usize_success_count(targets.len())) -} +literal("rate").then( + argument("rate", ArgumentType::float(1.0, 10_000.0)) + .executes(set_rate), +) ``` -For simple typed arguments, use `arguments.get::("name")` and convert parser errors with the local command helper: +Read parsed values through typed `SteelCommandContext` helpers: ```rust -let rate = arguments - .get::("rate") - .map_err(super::invalid_parsed_argument)?; +fn inspect( + context: &SteelCommandContext, +) -> Result { + let targets = context.players("target")?; + i32::try_from(targets.len()) + .map_err(|_| CommandSyntaxError::dynamic("Too many targets")) +} ``` -Parsers provide server parsing, client parser metadata, examples for ambiguity checks, and suggestions. The server parser can be stricter than the client metadata when the Minecraft client does not have a matching native parser. +Use the same argument name in the graph and accessor. Some accessors return `Option`; convert a missing value into `CommandSyntaxError` instead of assuming it exists. + +Argument types provide server parsing, client parser metadata, and suggestions. Steel-owned keyed argument payloads keep custom argument values extensible without relying on Rust `TypeId` identity. ## Automatic Root Permissions -Commands receive a root permission unless the registration is public: +Unless overridden, a registration derives this permission from its ID: ```text -.command. +.command. ``` -Examples: +For example, `minecraft:give` derives `minecraft.command.give`, while `steel:fly` derives `steel.command.fly`. The root requirement controls parsing, the client command tree, and suggestions. -| Registration | Root literal | Permission | -| ------------ | ------------ | ---------- | -| `CommandRegistrationSpec::minecraft()` | `give` | `minecraft.command.give` | -| `CommandRegistrationSpec::steel()` | `fly` | `steel.command.fly` | - -The root permission gates parsing, suggestions, and the command tree sent to players. Console and RCON pass permission requirements automatically. - -Use `.public()` for commands that should not require a permission: +Unset permissions are normally denied. For a generally available command, use `.default_access()`: ```rust -pub(crate) const REGISTRATION: CommandRegistrationSpec = - CommandRegistrationSpec::minecraft().public(); -``` - -## Aliases and Permission Bases - -Aliases register extra root literals for the same command graph: - -```rust -pub(crate) const REGISTRATION: CommandRegistrationSpec = - CommandRegistrationSpec::minecraft() - .permission_base("teleport") - .aliases(&["teleport"]); - -pub(crate) fn command() -> CommandNodeBuilder { - literal("tp") +pub(super) fn registration() -> CommandRegistration { + CommandRegistration::new(Identifier::vanilla_static("list"), |_| command()) + .default_access() } ``` -This makes `/tp` and `/teleport` use `minecraft.command.teleport`. - -Use `.permission_base("name")` when the root literal should not become the permission segment. This is required for aliases like `/tp` where the canonical permission should be `teleport`. +Default access allows an unset permission but still respects an explicit deny. This differs from bypassing permission checks. -Alias names are validated as command literals. The permission base is validated as a permission segment. - -## Subcommand Permissions - -Use `.requires_subcommand_permission()` on a non-root literal when a subcommand can be granted independently from the root command: +Use `.permission(expression)` when a command needs a custom or compound root policy: ```rust -pub(crate) fn command() -> CommandNodeBuilder { - literal("tick") - .then(literal("query").executes(query_tick)) - .then( - literal("freeze") - .requires_subcommand_permission() - .executes(freeze_tick), - ) -} +let permission = PermissionExpr::key(PermissionKey::parse("plugin.command.inspect")?); +CommandRegistration::new(Identifier::new_static("plugin", "inspect"), |_| command()) + .permission(permission) ``` -For a Minecraft command rooted at `/tick`, this derives: +An explicit permission expression cannot be combined with derived subcommand permissions on the same registration. -```text -minecraft.command.tick.freeze -``` - -The user may hold either `minecraft.command.tick` or `minecraft.command.tick.freeze`. Holding only the child permission exposes the root command enough to reach that child. +## Aliases and Collisions -Use `.requires_additional_subcommand_permission()` when the user must hold both the root command permission and the derived child permission: +The ID path is the canonical root. Add unqualified aliases with `.alias(...)`: ```rust -literal("delete") - .requires_additional_subcommand_permission() - .executes(delete_group) +CommandRegistration::new(Identifier::vanilla_static("teleport"), |_| command()) + .alias("tp") ``` -This is useful for sensitive actions inside a broader command family. +Both `/teleport` and `/tp` use `minecraft.command.teleport`; aliases do not create new permission keys. -Use `.permission_path_passthrough()` for syntax-only literals that should not appear in derived permission keys. +Command IDs must be unique. Earlier registrations win an unqualified root or alias collision. If a command has a collision, Steel also registers its namespaced ID (for example, `/minecraft:teleport`) as a fallback. Namespaced aliases are reserved for these collision fallbacks and cannot be declared manually. -## Dynamic Argument Permissions +## Subcommand Permissions -Some commands derive a permission segment from a parsed argument value. `/gamemode` is the current built-in example: +Declare independently grantable literal paths on the registration: ```rust -pub(crate) fn command() -> CommandNodeBuilder { - literal("gamemode").then( - argument("gamemode", GameModeParser) - .requires_argument_permission::("gamemode") - .executes(set_own_game_mode), - ) -} -``` - -`GameType` implements `CommandPermissionArgument`, so these value permissions are available: - -```text -minecraft.command.gamemode.survival -minecraft.command.gamemode.creative -minecraft.command.gamemode.adventure -minecraft.command.gamemode.spectator +CommandRegistration::new(Identifier::vanilla_static("tick"), |_| command()) + .subcommand_permission(["rate"]) + .subcommand_permission(["step"]) + .subcommand_permission(["freeze"]) ``` -The root permission `minecraft.command.gamemode` grants all values, but a more specific value rule can override it. Suggestions are filtered the same way, so a player with only `minecraft.command.gamemode.survival` only sees `survival`. +This publishes `minecraft.command.tick.rate`, `minecraft.command.tick.step`, and `minecraft.command.tick.freeze`. A user may hold either the root permission or the relevant child permission. A specific child deny can override a broad root allow because each node uses a scoped permission expression. -When adding a dynamic permission argument: +Paths contain literal names only. Registration walks through argument nodes while matching them, so a declaration such as `["user", "info"]` can match `/perms user info`. Missing, duplicate, ambiguous, empty, or invalid paths fail dispatcher construction. -1. Implement `CommandPermissionArgument` for the parsed value type. -2. Convert the parsed value into one `PermissionSegment`. -3. Return all possible segments from `catalog_permission_segments()`. -4. Use `.requires_argument_permission::("argument_name")` after the matching argument node. - -The catalog must be finite so Steel can publish child permissions for suggestions and management tools. +Dynamic value permissions are expressed directly when building the registration policy and checked by the executor. `/gamemode` is the built-in example: its visible root policy is an `Any` expression over scoped permissions such as `minecraft.command.gamemode.creative`, and execution checks the selected mode again. ## Permission Expressions -Command requirements can use `PermissionExpr` directly for compound checks: - -```rust -let root = PermissionKey::parse("minecraft.command.gamemode")?; -let creative = PermissionKey::parse("minecraft.command.gamemode.creative")?; - -let permission = PermissionExpr::scoped_key(root, creative); -``` - -`PermissionExpr` supports: +`PermissionExpr` composes command authorization: | Expression | Meaning | -| ---------- | ------- | -| `PermissionExpr::key(key)` | Require one key | -| `PermissionExpr::scoped_key(parent, key)` | Parent grants the child unless a more specific child rule overrides it | -| `left & right` | Require all child expressions | -| `left | right` | Require at least one child expression | - -This expression model is for command requirements. Group config and `/steelperms` rule strings use a single key plus optional context selector, such as `minecraft.command.gamemode{domain=lobby}`. - -## Registration Validation - -Normal command registration resolves derived and dynamic permission markers. Calling `.build()` directly on a tree that still contains those markers fails. - -Registration also validates the command graph before committing it: - -- root commands must be literal nodes -- aliases must be valid command literal names -- permission bases must be valid permission segments -- terminal arguments cannot have children or redirects -- ambiguous child parsers are rejected -- failed alias registration does not leave the primary root registered +| --- | --- | +| `PermissionExpr::key(key)` | Checks one key | +| `PermissionExpr::scoped_key(parent, key)` | Lets the parent grant a child while preserving a more-specific child rule | +| `left & right` | Requires every expression | +| `left \| right` | Requires at least one expression | -## Client Command Tree and Suggestions +Configuration and `/perms` rule arguments are not compound command expressions. They contain one permission key and an optional context selector, such as `minecraft.command.gamemode{domain=lobby}`. -`CommandDispatcher` builds a filtered command tree for each player. Nodes whose requirements fail are omitted, and permission-backed nodes are marked with the protocol restricted flag. +## Validation and Discovery -The same graph and requirements are used for parsing and suggestions. This means command permissions affect: +`CommandDispatcherBuilder` validates and constructs the complete dispatcher atomically. It rejects invalid IDs and aliases, duplicate IDs, mismatched or non-literal roots, invalid subcommand paths, and invalid explicit permission keys. A failed build does not expose a partially registered dispatcher. -- whether a player can run the command -- which command nodes the client sees -- tab completion -- server-side suggestions -- value suggestions for dynamic permissions +The builder also collects root and subcommand permission keys for `/perms` suggestions. Register non-command permissions explicitly with `declare_permission(...)` when they should be discoverable. -The permission context is captured from the command source at command start. Later `/execute` changes to world, entity, or position do not change the permission authority for that command. +The filtered graph is used consistently for parsing, the client command tree, and server suggestions. Nodes whose authorization requirements fail are omitted. Permission context comes from the command source, including its world and domain; console and RCON sources bypass player permission rules. From 65a5ca303e776bb112199e817548b73084b0eaf0 Mon Sep 17 00:00:00 2001 From: 4lve Date: Sat, 11 Jul 2026 19:28:22 +0200 Subject: [PATCH 3/5] Move perms command into its own category --- astro.config.ts | 4 + src/content/docs/commands/overview.md | 40 ++ src/content/docs/commands/permissions.md | 407 ++++++++++++++++++ src/content/docs/configuration/permissions.md | 65 +-- 4 files changed, 458 insertions(+), 58 deletions(-) create mode 100644 src/content/docs/commands/overview.md create mode 100644 src/content/docs/commands/permissions.md diff --git a/astro.config.ts b/astro.config.ts index f0eb400..7d15a89 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -111,6 +111,10 @@ export default defineConfig({ }, autogenerate: { directory: "configuration" }, }, + { + label: "Commands", + autogenerate: { directory: "commands" }, + }, { label: "Development", translations: { diff --git a/src/content/docs/commands/overview.md b/src/content/docs/commands/overview.md new file mode 100644 index 0000000..e70f8dc --- /dev/null +++ b/src/content/docs/commands/overview.md @@ -0,0 +1,40 @@ +--- +title: Command Reference +description: Conventions and detailed references for SteelMC commands. +sidebar: + order: 1 +--- + +This section documents SteelMC command families that need more than a short syntax list. Each reference explains what an operation changes, which permissions it requires, and how to use it safely. + +## Reading Command Syntax + +Command references use angle brackets for values you must provide: + +```text +/command +``` + +Do not type the brackets. For example, `/perms group info` becomes: + +```text +/perms group moderator info +``` + +Player target arguments can accept a player name or a compatible Minecraft target selector. Commands that use profile targets can also resolve known offline players. + +## Permissions + +Most commands derive a permission from their namespaced command ID: + +```text +.command. +``` + +Some command families publish more specific permissions for individual operations. The root permission grants those operations unless a more specific rule denies one. Console and RCON bypass player permission checks. + +For details about permission keys, groups, contexts, and conflict resolution, see [Permission configuration](../../configuration/permissions). + +## Available References + +- [Permission commands](../permissions) — inspect and manage player rules, groups, inheritance, defaults, and metadata with `/perms` diff --git a/src/content/docs/commands/permissions.md b/src/content/docs/commands/permissions.md new file mode 100644 index 0000000..791c5df --- /dev/null +++ b/src/content/docs/commands/permissions.md @@ -0,0 +1,407 @@ +--- +title: Permission Commands +description: Inspect and manage SteelMC permissions, groups, inheritance, and metadata with /perms. +sidebar: + order: 2 +--- + +`/perms` manages permissions while the server is running. It has three scopes: + +```text +/perms user +/perms group +/perms groups +``` + +- `user` manages direct player overrides and group assignments. +- `group` manages one named group's rules, priority, inheritance, and metadata. +- `groups` lists groups and manages which groups every player receives by default. + +There are no `/steelperms` or `/sp` aliases. + +:::tip +Inspect a player or group before editing it. Direct rules, inherited rules, contextual rules, and group priority can make the effective result different from a single configuration entry. +::: + +## How Authorization Works + +Every operation has a granular command permission. For example, player inspection requires `steel.command.perms.user.info`. The root `steel.command.perms` permission grants every `/perms` operation unless a more specific operation is denied. + +Changing or displaying managed data may require additional authority: + +| Authority | Purpose | +| --- | --- | +| `steel.permission.manage.` | Inspect, check, or change rules for that permission key | +| `steel.permission.group.` | Inspect, assign, or change that group | +| `steel.permission.metadata` | Display, check, or change permission metadata | + +Wildcards can grant a range of authority. For example, `steel.permission.manage.minecraft.command.*` permits management of descendant keys such as `minecraft.command.give`, while `steel.permission.manage.*` permits management of every permission key. Similarly, `steel.permission.group.*` permits management of every group. + +Information commands filter their output to the sender's authority. For example, user info omits metadata without `steel.permission.metadata` and omits rules or groups the sender cannot manage. + +Console and RCON bypass these player permission checks. + +## Inspect Current Settings + +These are the best commands to run before making changes. + +### Inspect a player + +```text +/perms user info +``` + +Shows each target's assigned groups, direct permission rules, and direct metadata overrides. This reports stored player settings, not every inherited effective rule. + +Command permission: `steel.command.perms.user.info` + +The output is filtered by the sender's permission-management, group-management, and metadata authority. + +```text +/perms user Steve info +``` + +### Check a player's effective permission + +```text +/perms user check +``` + +Resolves the permission for each target, including default groups, assigned groups, direct overrides, inheritance, priority, and context. The result identifies the winning rule and its source, or reports that the permission is unset. + +Command permission: `steel.command.perms.user.check` + +Additional authority: `steel.permission.manage.` for the checked key. + +```text +/perms user Steve check minecraft.command.gamemode.creative +/perms user Steve check minecraft.command.gamemode{domain=lobby} +``` + +### Inspect a group + +```text +/perms group info +``` + +Shows the group's priority, parent groups, allow rules, deny rules, and metadata. Entries outside the sender's management authority are omitted. + +Command permission: `steel.command.perms.group.info` + +Additional authority: `steel.permission.group.`. Metadata also requires `steel.permission.metadata`; individual permission rules require matching `steel.permission.manage.` authority. + +```text +/perms group moderator info +``` + +### List groups + +```text +/perms groups list +``` + +Lists the groups the sender has authority to manage and indicates which are default groups. + +Command permission: `steel.command.perms.groups.list` + +Only groups covered by the sender's `steel.permission.group.` authority are shown. + +## Manage Player Permission Rules + +A permission expression is a permission key with an optional context selector: + +```text +{=,...} +``` + +See [Contextual rules](../../configuration/permissions#contextual-rules) for the complete syntax and resolution behavior. + +### Allow a permission + +```text +/perms user allow +``` + +Adds or replaces the exact direct rule with an allow. Other contexts for the same permission remain unchanged. + +Command permission: `steel.command.perms.user.allow` + +Additional authority: `steel.permission.manage.`. + +```text +/perms user Steve allow minecraft.command.teleport +/perms user Steve allow minecraft.command.gamemode{domain=lobby} +``` + +### Deny a permission + +```text +/perms user deny +``` + +Adds or replaces the exact direct rule with a deny. A specific deny can override a broader allow. + +Command permission: `steel.command.perms.user.deny` + +Additional authority: `steel.permission.manage.`. + +```text +/perms user Steve deny minecraft.command.gamemode.creative +``` + +### Remove a direct rule + +```text +/perms user unset +``` + +Removes only the direct rule with the same permission key and exact context. Rules inherited from groups are not removed. + +Command permission: `steel.command.perms.user.unset` + +Additional authority: `steel.permission.manage.`. + +```text +/perms user Steve unset minecraft.command.gamemode{domain=lobby} +``` + +## Assign Player Groups + +### Add a group + +```text +/perms user group add +``` + +Assigns the group to each target. Assigning an already present group makes no change. + +Command permission: `steel.command.perms.user.group.add` + +Additional authority: `steel.permission.group.`. + +```text +/perms user Steve group add moderator +``` + +### Remove a group + +```text +/perms user group remove +``` + +Removes the assigned group from each target. This does not remove a group received through `default_groups` or inheritance. + +Command permission: `steel.command.perms.user.group.remove` + +Additional authority: `steel.permission.group.`. + +```text +/perms user Steve group remove moderator +``` + +## Manage Groups + +### Create or delete a group + +```text +/perms group create +/perms group delete +``` + +`create` adds an empty group. `delete` removes the group from the group configuration. Steel rejects deletion if the group is `op`, is still a default, or is inherited by another group; remove those references first. + +Command permissions: `steel.command.perms.group.create` and `steel.command.perms.group.delete`. + +Additional authority: `steel.permission.group.`. + +```text +/perms group moderator create +/perms group retired-staff delete +``` + +### Allow, deny, or unset a group rule + +```text +/perms group allow +/perms group deny +/perms group unset +``` + +`allow` and `deny` add or replace the exact group rule. `unset` removes only the rule with the same permission key and exact context. + +Command permissions: + +- `steel.command.perms.group.allow` +- `steel.command.perms.group.deny` +- `steel.command.perms.group.unset` + +Additional authority: both `steel.permission.group.` and `steel.permission.manage.`. + +```text +/perms group moderator allow minecraft.command.teleport +/perms group moderator deny minecraft.command.stop +/perms group moderator unset minecraft.command.teleport +``` + +### Change group priority + +```text +/perms group priority +``` + +Sets the signed 32-bit priority used when equally specific rules from different groups conflict. Higher priority wins; specificity still takes precedence over priority. + +Command permission: `steel.command.perms.group.priority` + +Additional authority: `steel.permission.group.`. + +```text +/perms group moderator priority 10 +``` + +## Manage Group Inheritance + +### List parent groups + +```text +/perms group inherit list +``` + +Lists the group's direct parents. Parent entries outside the sender's group-management authority are omitted. + +Command permission: `steel.command.perms.group.inherit.list` + +Additional authority: `steel.permission.group.`. + +```text +/perms group moderator inherit list +``` + +### Add or remove a parent + +```text +/perms group inherit add +/perms group inherit remove +``` + +Adds or removes a direct inheritance relationship. Adding a relationship that would create a cycle is rejected. Inherited rules keep the priority of the group where they were defined. + +Command permissions: `steel.command.perms.group.inherit.add` and `steel.command.perms.group.inherit.remove`. + +Additional authority: `steel.permission.group.` and `steel.permission.group.`. + +```text +/perms group moderator inherit add helper +/perms group moderator inherit remove helper +``` + +## Manage Metadata + +Metadata expressions use a namespaced key and the same optional context syntax as permission expressions: + +```text +:{=,...} +``` + +Values are explicitly typed as `int`, `bool`, or `string`. See [Metadata](../../configuration/permissions#metadata) for resolution rules. + +Quote string values that contain spaces: + +```text +/perms user Steve metadata set string "Senior Builder" plugin:rank +``` + +### Set player metadata + +```text +/perms user metadata set int +/perms user metadata set bool +/perms user metadata set string +``` + +Sets a direct metadata override for each target. It replaces only the value with the same metadata key and exact context. + +Command permission: `steel.command.perms.user.metadata.set` + +Additional authority: `steel.permission.metadata`. + +```text +/perms user Steve metadata set int 5 plugin:max_homes +/perms user Steve metadata set string gold plugin:chat/color{domain=lobby} +``` + +### Check player metadata + +```text +/perms user metadata check +``` + +Resolves the effective value for each target at the requested context and reports the winning source. This includes group metadata and direct overrides. + +Command permission: `steel.command.perms.user.metadata.check` + +Additional authority: `steel.permission.metadata`. + +```text +/perms user Steve metadata check plugin:max_homes{world=survival:overworld} +``` + +### Remove player metadata + +```text +/perms user metadata unset +``` + +Removes only the direct metadata entry with the same key and exact context. Inherited group metadata remains unchanged. + +Command permission: `steel.command.perms.user.metadata.unset` + +Additional authority: `steel.permission.metadata`. + +```text +/perms user Steve metadata unset plugin:max_homes +``` + +### Set or remove group metadata + +```text +/perms group metadata set int +/perms group metadata set bool +/perms group metadata set string +/perms group metadata unset +``` + +Sets or removes an exact metadata entry on the group. Use `/perms group info` to inspect configured group metadata; there is no separate group metadata `check` operation. + +Command permissions: `steel.command.perms.group.metadata.set` and `steel.command.perms.group.metadata.unset`. + +Additional authority: `steel.permission.group.` and `steel.permission.metadata`. + +```text +/perms group vip metadata set int 10 plugin:max_homes +/perms group vip metadata unset plugin:max_homes +``` + +## Manage Default Groups + +```text +/perms groups default add +/perms groups default remove +``` + +Adds or removes a group from `default_groups`. Default groups contribute to every player's effective permissions and metadata. Removing a default does not delete the group. + +Command permissions: `steel.command.perms.groups.default.add` and `steel.command.perms.groups.default.remove`. + +Additional authority: `steel.permission.group.`. + +```text +/perms groups default add member +/perms groups default remove member +``` + +## Persistence and Offline Players + +Group operations update `config/groups.toml`. Player operations update `/global/player_permissions.toml` and can resolve online players, known offline players, or profile names available through the server's profile lookup. + +These operations run without blocking the server tick. Command execution remains suspended until an operation finishes, then the sender receives its result and feedback. diff --git a/src/content/docs/configuration/permissions.md b/src/content/docs/configuration/permissions.md index 6f7cfb7..dcef703 100644 --- a/src/content/docs/configuration/permissions.md +++ b/src/content/docs/configuration/permissions.md @@ -233,72 +233,21 @@ The player can use most Minecraft commands, but not `/stop`. ## Runtime Commands -Use `/perms` to inspect and edit permissions while the server is running. The root permission is `steel.command.perms`; there are no `/steelperms` or `/sp` aliases. - -Useful commands: - -```text -/perms user info -/perms user check -/perms user allow -/perms user deny -/perms user unset - -/perms user group add -/perms user group remove - -/perms group create -/perms group info -/perms group delete -/perms group allow -/perms group deny -/perms group unset -/perms group priority -/perms group inherit list -/perms group inherit add -/perms group inherit remove - -/perms groups list -/perms groups default add -/perms groups default remove -``` - -Metadata can also be edited: +Use `/perms` to inspect and edit permissions while the server is running. Its general shape is: ```text -/perms user metadata set int -/perms user metadata set bool -/perms user metadata set string -/perms user metadata check -/perms user metadata unset - -/perms group metadata set int -/perms group metadata set bool -/perms group metadata set string -/perms group metadata unset +/perms ... ``` -`/perms user ... metadata check` resolves the effective value at the context written in `` and reports the winning source. Group metadata has no separate `check` command; use group info to inspect configured entries. - -`` is a permission key plus an optional context selector, such as: - -```text -minecraft.command.gamemode{domain=lobby} -``` - -`` uses the same context selector syntax: +Start by inspecting the current configuration: ```text -plugin:homes{world=survival:overworld} +/perms user info +/perms group info +/perms groups list ``` -`/perms` operations can resolve offline profiles and persist changes without blocking the server tick. Command execution remains suspended until the operation finishes, then the sender receives its result and feedback. - -### Administration Permissions - -Each branch also has a granular command permission, such as `steel.command.perms.user.info`, `steel.command.perms.user.allow`, or `steel.command.perms.group.inherit.add`. Holding `steel.command.perms` grants every branch unless a more specific branch is denied. - -Editing a rule additionally requires authority over its target permission. `steel.permission.manage.*` grants authority over all permission keys, while narrower keys such as `steel.permission.manage.minecraft.command.*` limit what the administrator can change. Group administration uses the corresponding `steel.permission.group.*` authority. Viewing metadata in user/group info and checking or editing metadata requires `steel.permission.metadata`; without it, metadata is omitted from info output. These non-command permissions are published for `/perms` autocomplete. +These commands show direct player settings, group rules and inheritance, and the available groups before you make changes. See [Permission commands](../../commands/permissions) for every operation, required permission, and example. ## Operators From 0a3afa09387e785ddaace126682d805a81361a85 Mon Sep 17 00:00:00 2001 From: 4lve Date: Sat, 11 Jul 2026 20:48:53 +0200 Subject: [PATCH 4/5] some improvments --- src/content/docs/commands/overview.md | 10 +- src/content/docs/commands/permissions.md | 2 +- src/content/docs/configuration/permissions.md | 102 +++++++++++++----- 3 files changed, 82 insertions(+), 32 deletions(-) diff --git a/src/content/docs/commands/overview.md b/src/content/docs/commands/overview.md index e70f8dc..629d431 100644 --- a/src/content/docs/commands/overview.md +++ b/src/content/docs/commands/overview.md @@ -7,6 +7,12 @@ sidebar: This section documents SteelMC command families that need more than a short syntax list. Each reference explains what an operation changes, which permissions it requires, and how to use it safely. +## Command Index + +- [`/perms`](../permissions) — inspect and manage player rules, groups, inheritance, defaults, and metadata + +Vanilla commands follow their normal Minecraft syntax. Until a command has Steel-specific behavior that needs its own reference, use the [Minecraft Wiki command list](https://minecraft.wiki/w/Commands#List_and_summary_of_commands). + ## Reading Command Syntax Command references use angle brackets for values you must provide: @@ -34,7 +40,3 @@ Most commands derive a permission from their namespaced command ID: Some command families publish more specific permissions for individual operations. The root permission grants those operations unless a more specific rule denies one. Console and RCON bypass player permission checks. For details about permission keys, groups, contexts, and conflict resolution, see [Permission configuration](../../configuration/permissions). - -## Available References - -- [Permission commands](../permissions) — inspect and manage player rules, groups, inheritance, defaults, and metadata with `/perms` diff --git a/src/content/docs/commands/permissions.md b/src/content/docs/commands/permissions.md index 791c5df..a11a903 100644 --- a/src/content/docs/commands/permissions.md +++ b/src/content/docs/commands/permissions.md @@ -17,7 +17,7 @@ sidebar: - `group` manages one named group's rules, priority, inheritance, and metadata. - `groups` lists groups and manages which groups every player receives by default. -There are no `/steelperms` or `/sp` aliases. +This reference assumes familiarity with permission keys, wildcards, groups, and contexts. Start with [Permission configuration](../../configuration/permissions) if those concepts are new to you. :::tip Inspect a player or group before editing it. Direct rules, inherited rules, contextual rules, and group priority can make the effective result different from a single configuration entry. diff --git a/src/content/docs/configuration/permissions.md b/src/content/docs/configuration/permissions.md index dcef703..4bedec3 100644 --- a/src/content/docs/configuration/permissions.md +++ b/src/content/docs/configuration/permissions.md @@ -22,27 +22,9 @@ Edit `groups.toml` for normal server policy. The player permissions file is unde ## Default Groups -A new server creates this basic group configuration: +A new server has two groups: `default` and `op`. Every player receives `default` because it is listed in `default_groups`. The required `op` group grants `*` and is assigned by `/op`. -```toml -default_groups = ["default"] - -[groups.default] -priority = 0 -inherits = [] -allow = [] -deny = [] -metadata = [] - -[groups.op] -priority = 0 -inherits = [] -allow = ["*"] -deny = [] -metadata = [] -``` - -Every player receives all groups listed in `default_groups`. The `op` group is required, grants `*` by default, and is what `/op` assigns. +Add server roles as tables named `[groups.]`. Each group can contain permission rules, metadata, and parent groups to inherit from. A [complete example](#complete-example) later on this page shows how these pieces fit together. :::caution Do not put `op` in `default_groups` unless every player should have every permission. @@ -70,6 +52,10 @@ Wildcards are allowed only as the final segment: `minecraft.command.*` matches `minecraft.command.give`, but it does not match `minecraft.command` itself. +:::note +A wildcard matches descendants only. Grant both `minecraft.command` and `minecraft.command.*` if a system defines meaningful permissions at both levels. +::: + ## Command Permissions Most built-in commands automatically use this permission shape: @@ -129,15 +115,24 @@ Group names must be valid permission segments: lowercase letters, digits, `_`, a Groups can inherit the permissions and metadata of other groups: ```toml +[groups.helper] +priority = 5 +inherits = [] +allow = ["minecraft.command.teleport"] +deny = [] +metadata = [] + [groups.moderator] priority = 10 inherits = ["helper"] -allow = ["minecraft.command.teleport"] +allow = ["minecraft.command.gamemode.spectator"] deny = [] metadata = [] ``` -Inheritance is transitive, and each inherited group contributes at most once. Cycles and references to missing groups make the configuration invalid. An inherited rule keeps the priority of the group where it was defined; it does not take on the child group's priority. +Members of `moderator` receive both permissions. The inherited teleport rule keeps the `helper` priority of `5`; it does not take on the `moderator` priority of `10`. + +Inheritance is transitive, and each inherited group contributes at most once. Cycles and references to missing groups make the configuration invalid. ### Contextual Rules @@ -154,9 +149,7 @@ allow = [ deny = [ "minecraft.command.gamemode.creative{world=lobby:spawn}", ] -metadata = [ - { key = "plugin:homes{domain=lobby}", value = 3 }, -] +metadata = [] ``` Built-in context keys: @@ -174,7 +167,11 @@ Multiple selectors are combined with AND: plugin.region.build{world=lobby:spawn,plugin:region=market} ``` -Each context key can appear only once in an expression. A world automatically implies the domain in its namespace, so `world=lobby:spawn` also matches rules scoped to `domain=lobby`. If both are written, the domain must match the world's namespace; Steel removes the redundant domain when it stores the expression. Conflicting pairs such as `{domain=survival,world=lobby:spawn}` are invalid. +Each context key can appear only once in an expression. + +:::note +`world=lobby:spawn` already includes the `lobby` domain and therefore matches rules scoped to `domain=lobby`. Writing both is unnecessary. If both are present, they must agree; `{domain=survival,world=lobby:spawn}` is invalid. +::: Context values cannot be empty and cannot contain whitespace, `{`, `}`, `,`, or `=`. Domain names use Minecraft identifier-namespace syntax, and worlds must be written as `:`. @@ -192,12 +189,13 @@ allow = [] deny = [] metadata = [ { key = "plugin:homes", value = 10 }, + { key = "plugin:homes{domain=lobby}", value = 3 }, { key = "plugin:chat/color", value = "gold" }, { key = "plugin:can_fly", value = true }, ] ``` -Metadata keys must be namespaced identifiers such as `plugin:homes` or `plugin:chat/color`. +Metadata keys must be namespaced identifiers such as `plugin:homes` or `plugin:chat/color`. The system that owns a key decides what its value means: for example, a homes plugin could interpret these rules as a limit of 10 homes normally and 3 inside the `lobby` domain. Steel stores and resolves the values but does not implement homes, chat colors, or flight merely because these example keys exist. Metadata resolution considers only entries with the requested exact metadata key. Among matching entries, Steel chooses: @@ -231,6 +229,56 @@ metadata = [] The player can use most Minecraft commands, but not `/stop`. +## Complete Example + +This configuration provides a practical starting point for a small server: + +```toml +default_groups = ["default"] + +[groups.default] +priority = 0 +inherits = [] +allow = [] +deny = [] +metadata = [] + +[groups.vip] +priority = 5 +inherits = [] +allow = [] +deny = [] +metadata = [ + { key = "example_homes:max", value = 5 }, + { key = "example_chat:color", value = "gold" }, +] + +[groups.moderator] +priority = 10 +inherits = [] +allow = [ + "minecraft.command.teleport", + "minecraft.command.gamemode.spectator", + "minecraft.command.tick.freeze", +] +deny = [] +metadata = [] + +[groups.op] +priority = 0 +inherits = [] +allow = ["*"] +deny = [] +metadata = [] +``` + +- Everyone receives `default`. +- Assign `vip` to players who should receive plugin-provided benefits. +- Assign `moderator` to staff who need the listed commands. +- `/op` assigns the required `op` group, which grants every permission by default. + +The `example_homes:max` and `example_chat:color` entries are illustrative. They have an effect only when plugins or server subsystems that own those keys read them. Replace them with metadata supported by the plugins you actually install. + ## Runtime Commands Use `/perms` to inspect and edit permissions while the server is running. Its general shape is: From 2dbcf9816c4311b22e669f3d9772bedb6c7cd294 Mon Sep 17 00:00:00 2001 From: 4lve Date: Mon, 13 Jul 2026 21:31:53 +0200 Subject: [PATCH 5/5] docs: emphasize permission syntax constraints --- src/content/docs/configuration/permissions.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/content/docs/configuration/permissions.md b/src/content/docs/configuration/permissions.md index 4bedec3..2e7eeb7 100644 --- a/src/content/docs/configuration/permissions.md +++ b/src/content/docs/configuration/permissions.md @@ -40,7 +40,9 @@ steel.command.fly plugin.region.build ``` -Segments can contain lowercase ASCII letters, digits, `_`, and `-`. Empty segments and uppercase letters are invalid. +:::caution +Permission key syntax is strict. Segments can contain only lowercase ASCII letters, digits, `_`, and `-`. Empty segments and uppercase letters are invalid. +::: Wildcards are allowed only as the final segment: @@ -167,13 +169,13 @@ Multiple selectors are combined with AND: plugin.region.build{world=lobby:spawn,plugin:region=market} ``` -Each context key can appear only once in an expression. +:::caution +Context syntax is strict. Each key can appear only once in an expression. -:::note `world=lobby:spawn` already includes the `lobby` domain and therefore matches rules scoped to `domain=lobby`. Writing both is unnecessary. If both are present, they must agree; `{domain=survival,world=lobby:spawn}` is invalid. -::: -Context values cannot be empty and cannot contain whitespace, `{`, `}`, `,`, or `=`. Domain names use Minecraft identifier-namespace syntax, and worlds must be written as `:`. +Values cannot be empty and cannot contain whitespace, `{`, `}`, `,`, or `=`. Domain names use Minecraft identifier-namespace syntax, and worlds must be written as `:`. +::: Context specificity is additive. Global rules are least specific, a domain or custom selector adds one level, and a world adds two because it identifies both a domain and a loaded world. A rule with several matching selectors is therefore more specific than a rule with only one of them.