Skip to content

The /cloud command

The agents add an in-game command, so somebody standing on a server can see the network the operator sees and, if you let them, change it. It is granted to nobody by default, which means a moderator who has not been given a permission node will not find it: it does not appear, it does not answer, it looks exactly as though it does not exist.

So the first thing to do is grant something. Spawnery ships no permission system; use whatever your server already runs. With LuckPerms, giving your moderators the reading half is:

/lp group moderator permission set spawnery.cloud.read true

and from then on they have:

/cloud list
/cloud info <name>

Everything else is a separate grant, deliberately.

The seven nodes

Node Opens
spawnery.cloud.read /cloud list, /cloud info <name>
spawnery.cloud.retire /cloud retire <name>, /cloud unretire <name>
spawnery.cloud.scale /cloud scale <group> <count> [for <duration>], /cloud scale <group> reset
spawnery.cloud.events /cloud events [minimal\|normal\|verbose\|off]
spawnery.cloud.status /cloud status [group\|server\|proxy]
spawnery.cloud.forcestop /cloud forcestop <server> (proxy only)
spawnery.cloud.execute /cloud execute <server\|group> <command> (proxy only, and only with spec.commands.execute)

None of them implies another. Retiring is its own node rather than a level above reading, because the two are not the same kind of thing: reading is what you give a moderator so they can see where people are, and retiring changes the fleet. A permission system that made one imply the other would hand every moderator the second the day somebody granted the first.

retire and unretire share one node for the same reason: somebody trusted to retire a server is trusted to take that back.

Pinning a group and resetting it share the scale node, for the same reason: whoever holds a group at a size must be able to let it go again, or a mistake would stay until somebody with more rights undid it.

Any one of the seven makes the bare /cloud root visible. That is deliberate too: a root demanding spawnery.cloud.read would hide the whole tree from somebody granted only spawnery.cloud.retire, and hide it in the worst possible way. The branches still gate themselves, so this widens what is visible and nothing else. On a backend the two proxy-only nodes open nothing.

Where a permission applies

Every Spawnery pod tells LuckPerms what it is, so a rule can name a place:

Context Value
group the ServerGroup or ProxyGroup
network the Network
environment paper on a backend, velocity on a proxy
server the pod's own name

So the moderators above can be given the reading half on the lobbies alone:

/lp group moderator permission set spawnery.cloud.read true group=lobby

group is the one to reach for. The group= at the end of that line is the Spawnery group, not the LuckPerms group the command names first. server is a pod name and changes every time a server is replaced, so it says where a player is rather than granting anything. A pod fills it in only when LuckPerms reported no server name of its own as the server started; set one in LuckPerms' config afterwards and the servers have to restart before they stop filling it in.

Nothing has to be switched on. The contexts appear when LuckPerms is installed and enabled, and nothing happens when it is not. If LuckPerms is installed but fails to enable, the pod logs the failure and runs without contexts. A LuckPerms reload may leave the pod without contexts until it restarts: the agent registers its context calculator once and nothing registers it again. This is read from the code, not reproduced.

What each branch does

/cloud list and /cloud info <name> are lookups in the agent's local mirror of the network. They read memory and nothing else, so they cannot block the server's main thread, time out, or fail because the operator is unreachable, which is what makes them safe to run from a chat message. info names a server's group, its phase and whether it is taking joins.

/cloud retire <name> is the exception: it changes an object in the cluster. It does not block either (the request is handed off and the answer arrives when the cluster replies), but unlike the reading branches it can fail for reasons outside the server it was typed on. Retiring puts a server into the soft drain described in Rolling a group: it stops taking joins, and the players already on it are left alone.

/cloud retire <name> takes a proxy's name too. A retired proxy is replaced first, then takes no new connections and stops once it is empty; nobody on it is disconnected.

/cloud unretire <name> takes a retirement back while the server is still retiring and not yet being stopped: it takes joins again, and it is held: nothing automatic removes it any more, not a rolling update and not a scale-down, and it stays until it ends by itself. A server that is already draining, terminating or finished is refused. /cloud info says "held" for such a server.

/cloud list shows each proxy under its proxy group, and /cloud info <name> answers for a proxy: ready or not, draining or not, and its players.

/cloud status shows how the network is doing: its pods' CPU and memory against what they requested, one line per group with the lowest TPS among its servers, and a line for the namespace's other pods. /cloud status <group> lists the group's servers or proxies with TPS, MSPT, usage and age; /cloud status <server|proxy> shows one of them against its requests and limits. It asks the operator, so it answers only while the agent is connected.

Usage comes from the cluster's metrics API (metrics-server). Without one the answer still arrives, with – where usage would be. A pod started within the last minute may not have a sample yet; the totals then say how many pods they cover. TPS – is a server that has not reported a tick rate, such as one running an agent older than 0.9.0.

Nothing outside the network's namespace is shown. A backend server's answer leaves private servers and on-demand groups out, as /cloud list does.

/cloud info and /cloud status name the node a server or proxy runs on, or say it is not scheduled yet. Nothing else about the node is shown.

Every answer opens with a heading and sorts what follows into sections; bars show players against playable seats (a server whose playable seats differ from its slots reads 9 / 12 · max 100), TPS against 20, and CPU and memory against their limit (or their request where a container has no limit). One-line answers begin with ✔ or ✘.

/cloud scale <group> <count> [for <duration>] holds an ephemeral group at exactly that many servers, 0 included, for the given time. A duration is a number and a unit, 30m, 2h or 3d; without one the pin lasts an hour, and the longest is 7 days. A count above the group's maxReplicas is refused, and so is a persistent or on-demand group, which are sized by spec.replicas and by requests. A pin below the group's minReplicas is accepted only from a proxy, so a plugin on a backend can pin within the floor and ceiling but cannot take a group under its floor. It creates an Exact ScaleBoost, described in Holding a group at a size. The operator creates it, so it has an owner reference to the group and goes with it.

Empty servers above the number are deleted. Occupied ones are retired, emptiest first: the proxies send nobody new, and a server goes once its players have left, or at spec.update.maxStaleSeconds if the group sets one. A server you took hold of with /cloud unretire stays. While a pin holds, a rolling update cannot surge above it, the same as for a group at maxReplicas. /cloud info <group> shows Pinned 0 servers until 18:00 UTC. /cloud scale <group> reset removes every pin and every boost on the group.

/cloud forcestop <server> kills the server's pod at once. There is no drain and no confirmation. Players lose their connection and the proxy moves them to the next fallback group. Unsaved world data is lost. What follows is the group's own rule: an ephemeral group builds a new server if it needs one, a persistent group restarts the same ordinal on its claim, and an on-demand member stays stopped with its world. /cloud info shows when the pod is gone. The server gets a ForceStopped event naming the issuer and the proxy.

/cloud execute <server|group> <command> is off until the Network says so:

kind: Network
spec:
  commands:
    execute: true

It then runs the command with console permissions on one server, or on every Ready server of a group. One server answers with up to 20 lines of output. A group answers with a line per server and a total such as 4 of 5 servers ran it. A server that stays silent for 8 seconds is listed as such, and so is one whose agent is not connected, which counts as not having run it. Feedback a command sends later, from another tick or thread, is not shown. A proxy is never a target. Each server gets a CommandExecuted event with the issuer and the command, never the output, and the operator logs network, proxy, issuer, target and command.

Switching it on means that anybody holding the node can run any console command on any server, with no allowlist, and on most servers that includes op.

/cloud events sets how much of the cloud event feed this player sees in chat. Without a word it shows the player's current level. on means minimal, which is the default, and off shows nothing. inherit drops the player's own level, so the one their LuckPerms groups set, or the default, counts again.

Event minimal normal verbose
a server or proxy appears [+] lobby-x7k2 [+] lobby-x7k2 starting; a proxy [✓] gateway-4d1 ready the operator's note
a server passes its ready gate [✓] lobby-x7k2 ready the operator's note
a server or proxy starts to leave [-] lobby-x7k2 leaving the operator's note
a server or proxy is gone [-] lobby-x7k2 [-] lobby-x7k2 stopped the operator's note
a warning or a failure [!] lobby-x7k2 did not start in time the same the operator's note
anything else the operator's note

Several lines of one kind in one group within a second become one ([+] 5 lobby). At verbose the merged line names the kind and lists up to six names (8 PodCreated in lobby (lobby-a, lobby-b, lobby-c, lobby-d, lobby-e, lobby-f and 2 more)), and a single event shows as name: note. Warnings are never merged. An on-demand member shows as its group and the first six characters of its key (challenge-3f2b1c). Hovering over a name shows it in full, and clicking it puts /cloud info <name> in the chat box.

The agent decides how each event shows, from the operator's event reasons. An agent older than its operator shows a reason it does not know only at verbose. An operator older than 0.20.0 records no ServerStopped, so servers get no [-] line, and one of 0.30.0 or older gives none for a proxy that went away outside a drain either. The feed is not replayed: an event recorded while an agent's stream to the operator is down, or while the operator restarts, does not reach that agent's chat.

With LuckPerms the level is the player's meta value spawnery-feed, read with inheritance, so /lp group admin meta set spawnery-feed normal covers every admin who has not chosen their own. It survives a roll and a change of proxy when the proxies share LuckPerms storage. With a LuckPerms messaging service the other servers reload the player when the service delivers the update; without one, at the player's next login there. Without LuckPerms the proxy keeps the level in memory until it restarts.

Both sides, one command

The command is the same on Paper backends and on Velocity proxies. That is a property of the code rather than of two implementations kept in step: it is written once, generic in the platform's source type, and nothing in it names Paper's CommandSourceStack or Velocity's CommandSource. What a platform can be asked for is two methods (can this source do the thing, and send it this text) and nothing else.

Which side a player types it on decides what they are looking at, not what the command can do.

Granting nothing is a choice, not an oversight

A network where nobody holds any of the seven nodes has no in-game surface at all, and that is a perfectly reasonable place to stay: everything /cloud does is also a kubectl away. The point of the command is the case where it is not: somebody who should be able to see which servers exist, or add capacity before an event, without being given a kubeconfig and the cluster access that comes with it.

The upgrade note for when this command first appeared is in Release notes.