# Operations ## Smoke test after deploy 1. `/networkcore status` - confirm "Database: ok" and "Network: connected". 2. Connect a staff alt. Run `/lookup `. You should see your own UUID and online status. 3. Connect a non-staff alt. Have a staff member run `/warn 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 ` - 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).