This commit is contained in:
2026-05-26 00:59:39 -04:00
commit 4f4f731bc7
35 changed files with 2417 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# Operations
## Smoke test after deploy
1. `/networkcore status` - confirm "Database: ok" and "Network: connected".
2. Connect a staff alt. Run `/lookup <your-main>`. You should see your own UUID and online status.
3. Connect a non-staff alt. Have a staff member run `/warn <alt> test`. The alt receives a chat message; nothing crashes.
4. `/sc test` from staff. Every other online staff member sees it. No non-staff sees it.
5. Look in MySQL:
```sql
SELECT * FROM staff_punishments ORDER BY issued_at DESC LIMIT 5;
```
The warn should be the most recent row.
## A staff member can't run staff commands
1. Check what groups Hytale shows for them. There's no direct command for this in Staff; have them run `/lookup <theirname>` - if it works, they're staff. If it doesn't, the group is wrong.
2. `PermissionsModule.get().getGroupsForUser(uuid)` is the source. Confirm the group name they're assigned matches one in `permissions.staff_groups` in `config.json` (case-sensitive).
3. Group changes typically require the player to reconnect.
## A ban isn't propagating
1. On the issuing server: `/networkcore servers` - is the destination server in the list? If no, the destination isn't actually on the network.
2. On the issuing server: confirm `messagebus_published_total` is incrementing in `/metrics`.
3. On the destination server: confirm `messagebus_received_total` is incrementing. If it's rising but `messagebus_rejected_total` is also rising fast, you have an auth token mismatch.
4. Confirm the destination server's `network.auth_token` matches the issuer's. Different tokens cause silent message drops.
5. As a last resort: the destination server queries MySQL on connect via `BanEnforcer`. Have the target try to reconnect. If the ban still doesn't apply, the row may not actually be in the DB - check `SELECT * FROM staff_punishments WHERE target_uuid = ?`.
## A muted player can still chat
1. The mute might have expired. Check `expires_at` against now.
2. `MuteCache` on the chat server might be stale. The cache loads on connect; if the mute was issued *while the player was already online*, the broadcast path is what populates the cache.
3. If broadcasts aren't arriving, see the previous section.
4. As a fallback, kick + rejoin the player. `PlayerSeenTracker.onConnect` calls `loadMuteIntoCache` which forces a fresh DB read.
## `players_seen.online` is stuck at 1 after a crash
The plugin runs `markServerOffline(serverId)` in `start()`, which sets `online=0` for all rows whose `last_server_id` matches this server. So a normal restart fixes it.
If you see online=1 for a player whose `last_server_id` belongs to a server that no longer exists (decommissioned, renamed): clear manually with `UPDATE staff_players_seen SET online=0 WHERE last_server_id = 'old-server-id'`.
## Auditing
Recent actions taken by a moderator:
```sql
SELECT issued_at, type, target_name, reason
FROM staff_punishments
WHERE issued_by_name = 'StaffName'
ORDER BY issued_at DESC
LIMIT 50;
```
All actions in the last hour:
```sql
SELECT * FROM staff_punishments
WHERE issued_at > (UNIX_TIMESTAMP() * 1000) - 3600000
ORDER BY issued_at DESC;
```
Players who have been banned but later unbanned:
```sql
SELECT target_name, COUNT(*) AS unbanned_bans
FROM staff_punishments
WHERE type IN ('BAN', 'TEMPBAN') AND revoked_at IS NOT NULL
GROUP BY target_uuid, target_name
ORDER BY unbanned_bans DESC;
```
## Things this plugin deliberately doesn't do
- **IP bans.** Privacy implications and uncertainty about the Hytale auth model. Use the punishment system on UUIDs.
- **Appeals workflow.** Staff just unban. There's no in-game appeal form. Build that externally if you want it.
- **Multiple active bans on the same player.** The repository's "active ban" query returns the most recent one. Stacking is unsupported by design - it complicates logic without changing outcomes.
- **Per-world or per-server bans.** Punishments are network-wide. If you need per-world, that's a different feature (likely a separate plugin built on top of this).