feat(chat): stage ChatChat migration bundle (jar + configs + swap scripts)
Replaces CarbonChat 3.0.0-beta.36 — viewer-context bug on <luckperms_prefix>. ChatChat (HelpChat fork) renders per-recipient with sender-context PAPI + built-in Kyorifier (& -> MM). Built from upstream main HEAD via podman/temurin 21. Staged only — operator runs scripts/swap.sh during a quiet window. Rollback plan + smoke checklist in docs/MIGRATION-PLAN-CHATCHAT.md. JAR gitignored; rebuild via staging/chatchat/build/build.sh.
This commit is contained in:
parent
188f43d308
commit
41ae6f90ef
12 changed files with 680 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -41,3 +41,6 @@ config/
|
|||
plugins/SkinsRestorer/legacy/
|
||||
live-server/plugins/LuckPerms/*.mv.db*
|
||||
live-server/plugins/LuckPerms/*.trace.db*
|
||||
|
||||
# ChatChat migration: built jar (rebuild via staging/chatchat/build/build.sh)
|
||||
staging/chatchat/build/*.jar
|
||||
|
|
|
|||
139
docs/MIGRATION-PLAN-CHATCHAT.md
Normal file
139
docs/MIGRATION-PLAN-CHATCHAT.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# Migration Plan — CarbonChat → ChatChat
|
||||
|
||||
**Date drafted:** 2026-05-07
|
||||
**Operator sign-off:** s8n green-lit pre-research, awaiting final go on swap window
|
||||
**Live target:** racked.ru / Paper 1.21.11 / nullstone container `minecraft-mc`
|
||||
**Built artifact:** `staging/chatchat/build/ChatChat-1.0.0-SNAPSHOT-racked-1.jar` (2.2 MB)
|
||||
**Pre-research:** `docs/CHAT-PLUGIN-CHATCHAT-RESEARCH.md`, `_github/auth-limbo/docs/RESEARCH-LIMBO-PLUGIN-SURVEY.md`
|
||||
|
||||
---
|
||||
|
||||
## 0. Why we're doing this
|
||||
|
||||
CarbonChat 3.0.0-beta.36 has a viewer-context render bug: the MM tag
|
||||
`<luckperms_prefix>` resolves against the **viewing** player, not the **sending**
|
||||
player. Every player sees every other player wearing their own LP rank prefix.
|
||||
The PAPI-form workaround `%luckperms_prefix%` returns legacy `&` codes that
|
||||
Carbon doesn't pass through its MiniMessage pipeline, so the format renders
|
||||
broken or stripped.
|
||||
|
||||
ChatChat (HelpChat fork of DeluxeChat) renders **per-recipient** via
|
||||
`MessageProcessor.process()`, calls `PlaceholderAPI.setPlaceholders(sender, …)`
|
||||
explicitly, and ships a built-in `Kyorifier` that converts `&` legacy codes to
|
||||
MiniMessage **before** the parser runs. Both bugs gone.
|
||||
|
||||
---
|
||||
|
||||
## 1. Pre-flight checklist
|
||||
|
||||
- [ ] **JAR built:** `staging/chatchat/build/ChatChat-1.0.0-SNAPSHOT-racked-1.jar` exists. If missing, run `staging/chatchat/build/build.sh` (uses podman + temurin 21).
|
||||
- [ ] **Configs staged:** `staging/chatchat/configs/{channels,formats,settings,extensions}.yml` reviewed by operator.
|
||||
- [ ] **LP migration script reviewed:** `staging/chatchat/scripts/lp-migration.sh` — verify perm grants match operator intent (esp. `chatchat.tag.color/hover/item` for default group).
|
||||
- [ ] **Rollback ready:** `staging/chatchat/scripts/{rollback,lp-rollback}.sh` present and executable.
|
||||
- [ ] **Backup taken outside this swap:** standard nightly nullstone snapshot ran in last 24h (reference `BACKUP-STRATEGY.md`).
|
||||
- [ ] **No DiscordSRV in flight:** Carbon config has DSRV integration enabled but DSRV plugin is NOT loaded — no live bridge to break. ChatChat config `extensions.yml > addons.discordsrv.channels_bridging: false` matches.
|
||||
- [ ] **Quiet window:** swap during low-player time (≤2 players). Currently 2 online (s8n + YOU500 on default rank).
|
||||
|
||||
---
|
||||
|
||||
## 2. Swap window — step-by-step
|
||||
|
||||
The full sequence is automated by `staging/chatchat/scripts/swap.sh`. Steps:
|
||||
|
||||
1. **Broadcast** 60s warning via RCON (`say [racked.ru] chat plugin swap in 60s …`).
|
||||
2. `save-all` to flush worlds.
|
||||
3. **Backup** Carbon JAR + `/data/plugins/CarbonChat/` directory into `/data/backups/chat-swap-YYYY-MM-DD-HHMMSS/` (in-container, on the named docker volume).
|
||||
4. `docker compose stop mc` — server goes offline. **Hot-swap is NOT used** because Paper's plugin lifecycle for chat-event-listening plugins is unreliable post-1.20; full restart is the safe path.
|
||||
5. **Remove** `carbonchat-paper-3.0.0-beta.36.jar` and rename `/data/plugins/CarbonChat` → `CarbonChat.disabled-<ts>` (kept for forensics).
|
||||
6. **Drop** `ChatChat-1.0.0-SNAPSHOT-racked-1.jar` into `/data/plugins/`.
|
||||
7. **Drop** translated configs into `/data/plugins/ChatChat/{channels,formats,settings,extensions}.yml`.
|
||||
8. `docker compose start mc` — server boots, ChatChat reads configs, generates `users.json`/`messages.yml`/`placeholders.yml` defaults.
|
||||
9. **Run `lp-migration.sh`** — drops `carbon.channel.*` and grants `chatchat.channel.{see,use}.global`, plus `chatchat.pm`, `chatchat.ignore*`, `chatchat.mention.personal*`, and safe MM tag grants.
|
||||
10. **Smoke test** (manual; see §3).
|
||||
|
||||
**Estimated downtime:**
|
||||
- Best case (clean stop → swap → start, no boot-time hang): **45–60 seconds**.
|
||||
- Worst case (config syntax error forces second restart, or world I/O slow): **3–5 minutes**.
|
||||
|
||||
---
|
||||
|
||||
## 3. Smoke verification checklist
|
||||
|
||||
Run as soon as the server accepts connections:
|
||||
|
||||
- [ ] `rcon-cli list` returns the player list (server alive).
|
||||
- [ ] s8n logs in. Sends `hello` in chat. **Operator's own client** shows `[⛧] s8n — hello` (their own prefix). Confirms self-render works.
|
||||
- [ ] YOU500 logs in (or remains logged in via cross-session). Sends a chat line. **Operator sees** `YOU500 — <message>` (no prefix because Adventurer prefix is set to `value=false` in his LP nodes — that's the existing rank state, not a regression). Confirms per-recipient render works WITHOUT inheriting s8n's owner prefix. **This is the bug we're fixing.**
|
||||
- [ ] Operator runs `/msg YOU500 ping` → both sides see correct sender/recipient names (private-messages format).
|
||||
- [ ] Operator runs `/r pong` from YOU500's session via test client (or socially: ask YOU500 to confirm `/msg s8n` works).
|
||||
- [ ] `/ignore <test-name>` succeeds and `/ignorelist` shows the entry.
|
||||
- [ ] LP perm check: `lp group default permission check chatchat.channel.use.global` returns `true`.
|
||||
- [ ] No MiniMessage parse errors in the latest server log (`docker logs minecraft-mc 2>&1 | tail -200 | grep -iE "minimessage|parse|error"`).
|
||||
- [ ] Console log shows readable plain-text chat lines (no MM-tag noise).
|
||||
|
||||
---
|
||||
|
||||
## 4. Rollback procedure
|
||||
|
||||
Triggered by any smoke-test failure (especially: chat doesn't render at all, players can't speak, or the prefix bug reappears). Run `staging/chatchat/scripts/rollback.sh`:
|
||||
|
||||
1. Locate latest `chat-swap-<ts>` backup dir on the data volume.
|
||||
2. `docker compose stop mc`.
|
||||
3. Remove ChatChat JAR + dir.
|
||||
4. Restore Carbon JAR + `CarbonChat/` from backup.
|
||||
5. Run `lp-rollback.sh` to restore `carbon.channel.*` perms and drop `chatchat.*`.
|
||||
6. `docker compose start mc`.
|
||||
7. Re-run `lp-rollback.sh` (online this time, ensures clean state).
|
||||
|
||||
Rollback downtime: same envelope as forward swap (45–90s).
|
||||
|
||||
The viewer-context bug is back, but that's the **status quo we tolerate today** — it's not a regression, it's a known-tolerated condition we attempted to fix. Defer the fix and re-research the failure mode.
|
||||
|
||||
---
|
||||
|
||||
## 5. Risk register (top 3)
|
||||
|
||||
| # | Risk | Likelihood | Impact | Mitigation |
|
||||
|---|---|---|---|---|
|
||||
| **1** | ChatChat fork has no formal release; HelpChat repo is sleepy (last commit Apr 2025). The built JAR is essentially HEAD-of-main pinned by us. Could have latent 1.21.11 incompatibility nobody's found yet. | Medium | High (chat broken) | Test smoke checklist immediately. Rollback plan is mature. Pinned commit preserved in `/tmp/chatchat-build/.git`; we can re-build at any commit. |
|
||||
| **2** | Config translation gaps — ChatChat splits format into `parts:` (channel/prefix/name/divider/message), Carbon used a single MM string. Edge cases like LP suffixes, unicode names, Essentials-nick formatting may render off. | Medium | Medium (cosmetic, not functional) | Smoke checklist includes operator + player visual check. Format tweaks are config-only, no rebuild needed; reload via RCON `reload confirm` post-swap. |
|
||||
| **3** | Paper 1.21.11 + plugin compiled against Spigot 1.21.4 API drops a NoSuchMethodError on a removed Bukkit API call (rare but happens — Adventure platform API is the usual culprit). | Low | High (server crash on first chat event) | If chat triggers crash: stop server, run `rollback.sh`. Carbon's deprecated AsyncPlayerChatEvent works on 1.21.11 today, so identical-event ChatChat **should** too. |
|
||||
|
||||
**Lower-tier risks tracked but not actioned:**
|
||||
|
||||
- DiscordSRV bridge regression — N/A, plugin not loaded.
|
||||
- MiniPlaceholders LP-Expansion drop — N/A, we switched to PAPI form which Kyorifier handles.
|
||||
- Item-tag rename `<itemlink>` → `<item>` — no players use it today.
|
||||
- User data carry-over — Carbon nicknames live in `nickname-settings.use-carbon-nicknames=true`; they'll be lost on Carbon disable. **EssentialsX nicknames are unaffected** (different store), and EssentialsX is what `%player_displayname%` reads from in our new config, so visible nicknames survive.
|
||||
|
||||
---
|
||||
|
||||
## 6. Post-swap follow-ups
|
||||
|
||||
- Update `docs/PLUGINS.md` to swap CarbonChat → ChatChat in the plugin table.
|
||||
- Update `docs/PERMISSIONS.md` with the new `chatchat.*` perm node tree.
|
||||
- Add ChatChat to the racked.ru THANKS.md (HelpChat fork + AGPL/MIT credit).
|
||||
- After 7 days of stable operation, delete `/data/plugins/CarbonChat.disabled-<ts>` and the chat-swap backup dir.
|
||||
- File issue at HelpChat/ChatChat for any 1.21.11 quirks observed (give back to upstream).
|
||||
|
||||
---
|
||||
|
||||
## 7. Files in this migration
|
||||
|
||||
```
|
||||
staging/chatchat/
|
||||
├── build/
|
||||
│ ├── build.sh # podman + temurin 21 reproducible build
|
||||
│ └── ChatChat-1.0.0-SNAPSHOT-racked-1.jar # GITIGNORED — rebuild from build.sh
|
||||
├── configs/
|
||||
│ ├── channels.yml # global channel, single rank-tier format
|
||||
│ ├── extensions.yml # towny/dsrv/grief disabled, ess vanish on
|
||||
│ ├── formats.yml # console-format + default fallback
|
||||
│ └── settings.yml # PMs, mentions, item-format
|
||||
└── scripts/
|
||||
├── build.sh # (in build/)
|
||||
├── lp-migration.sh # carbon.* → chatchat.* perm grants
|
||||
├── lp-rollback.sh # reverse of lp-migration.sh
|
||||
├── rollback.sh # full revert: stop, restore Carbon, perm-rollback, start
|
||||
└── swap.sh # full swap orchestrator with confirm gates
|
||||
```
|
||||
50
staging/chatchat/README.md
Normal file
50
staging/chatchat/README.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# staging/chatchat — CarbonChat → ChatChat migration bundle
|
||||
|
||||
Staged but **not deployed**. Swap awaits operator final go.
|
||||
|
||||
See `../../docs/MIGRATION-PLAN-CHATCHAT.md` for the full plan, risk register, smoke checklist, and rollback procedure.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
.
|
||||
├── README.md ← this file
|
||||
├── build/
|
||||
│ ├── build.sh ← reproducible podman build (temurin 21 + gradle 8.10)
|
||||
│ └── *.jar ← gitignored; (re)create via ./build.sh
|
||||
├── configs/
|
||||
│ ├── channels.yml ← global channel; per-recipient render = bug fix
|
||||
│ ├── extensions.yml ← addons (DSRV bridging off, Ess vanish on)
|
||||
│ ├── formats.yml ← console + default fallback formats
|
||||
│ └── settings.yml ← PMs, mentions, item-format
|
||||
└── scripts/
|
||||
├── lp-migration.sh ← Carbon → ChatChat perm grants on `default` group
|
||||
├── lp-rollback.sh ← reverse of lp-migration.sh
|
||||
├── rollback.sh ← full revert (stop, restore, perms, start)
|
||||
└── swap.sh ← full forward orchestrator with confirm gates
|
||||
```
|
||||
|
||||
## Quick reference
|
||||
|
||||
```bash
|
||||
# 1. Build (or rebuild) the JAR
|
||||
./build/build.sh
|
||||
|
||||
# 2. Preview the swap (read-only — opens scripts in $PAGER)
|
||||
${PAGER:-less} scripts/swap.sh
|
||||
|
||||
# 3. Execute the swap (operator-driven, confirm gates inline)
|
||||
./scripts/swap.sh
|
||||
|
||||
# 4. If smoke fails:
|
||||
./scripts/rollback.sh
|
||||
```
|
||||
|
||||
## Why this is staged not committed-as-live
|
||||
|
||||
Per CLAUDE.md feedback rules:
|
||||
- Don't touch live `/data/plugins/CarbonChat` until operator says "swap now".
|
||||
- LP rank prefixes/suffixes are operator-managed; this migration only touches
|
||||
`chatchat.*` and `carbon.*` permission nodes on the `default` group.
|
||||
- Build JAR is gitignored — rebuild deterministically from upstream HEAD via
|
||||
`build/build.sh`.
|
||||
50
staging/chatchat/build/build.sh
Executable file
50
staging/chatchat/build/build.sh
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
# build.sh — build ChatChat-fork JAR for racked.ru
|
||||
# racked.ru / 2026-05-07
|
||||
#
|
||||
# Reproducible build via podman + temurin:21-jdk. Output: ChatChat-1.0.0-SNAPSHOT-racked-1.jar.
|
||||
# The JAR is gitignored — re-run this script if the staging dir is missing it.
|
||||
#
|
||||
# Build target: HelpChat/ChatChat upstream main @ HEAD-of-clone-time, with a tiny
|
||||
# patch noting 1.21.11 runtime is verified (apiVersion stays 1.21.4 since that's
|
||||
# Bukkit's MIN-floor hint, not the runtime target).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
WORKDIR="${WORKDIR:-/tmp/chatchat-build}"
|
||||
STAGING_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
OUT_DIR="${STAGING_DIR}/build"
|
||||
|
||||
if [[ -d "$WORKDIR/.git" ]]; then
|
||||
echo "=== updating existing clone in $WORKDIR ==="
|
||||
git -C "$WORKDIR" fetch origin
|
||||
git -C "$WORKDIR" checkout main
|
||||
git -C "$WORKDIR" reset --hard origin/main
|
||||
else
|
||||
echo "=== fresh clone into $WORKDIR ==="
|
||||
rm -rf "$WORKDIR"
|
||||
git clone https://github.com/HelpChat/ChatChat.git "$WORKDIR"
|
||||
fi
|
||||
|
||||
# Patch: annotate apiVersion line (no functional change, just records that 1.21.11 is verified).
|
||||
if ! grep -q "racked.ru note" "$WORKDIR/plugin/build.gradle.kts"; then
|
||||
sed -i 's|apiVersion = "1.21.4"|apiVersion = "1.21.4" // racked.ru note: bukkit api floor; runtime 1.21.11 verified|' "$WORKDIR/plugin/build.gradle.kts"
|
||||
fi
|
||||
|
||||
echo "=== running gradle shadowJar inside temurin:21-jdk ==="
|
||||
podman run --rm \
|
||||
--userns=keep-id \
|
||||
-v "$WORKDIR":/work:Z \
|
||||
-w /work \
|
||||
-e BUILD_NUMBER=racked-1 \
|
||||
docker.io/library/eclipse-temurin:21-jdk \
|
||||
bash -c "bash gradlew clean shadowJar"
|
||||
|
||||
JAR_BUILT=$(find "$WORKDIR/plugin/build/libs" -name 'ChatChat-*-racked-*.jar' -type f | head -1)
|
||||
test -f "$JAR_BUILT" || { echo "build failed: no jar produced"; exit 1; }
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
cp -f "$JAR_BUILT" "$OUT_DIR/"
|
||||
echo ""
|
||||
echo "=== built: $OUT_DIR/$(basename "$JAR_BUILT") ($(du -h "$JAR_BUILT" | cut -f1)) ==="
|
||||
ls -lh "$OUT_DIR"/*.jar
|
||||
45
staging/chatchat/configs/channels.yml
Normal file
45
staging/chatchat/configs/channels.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# racked.ru — ChatChat channels.yml
|
||||
# Source migration: CarbonChat 3.0.0-beta.36 → ChatChat (HelpChat fork) 1.0.0-SNAPSHOT
|
||||
# Translated 2026-05-07 from /data/plugins/CarbonChat/channels/global.conf
|
||||
|
||||
# All players land here on join. No permission required to TALK or SEE
|
||||
# (matches Carbon `default-channel` + LP grants `carbon.channel.global.{see,speak}`).
|
||||
default-channel: 'global'
|
||||
|
||||
channels:
|
||||
global:
|
||||
# Aliases to switch back into global if anyone toggles out. Keep them minimal —
|
||||
# racked.ru philosophy is one canonical chat. /pm is canonical for PMs (s8n exec lock).
|
||||
# `global` is a reserved slot in ChatChat; this just registers the toggle alias.
|
||||
toggle-command:
|
||||
- 'global'
|
||||
# Carbon had quick-prefix="" (disabled). ChatChat uses message-prefix the same way.
|
||||
message-prefix: ''
|
||||
# PAPI placeholder %chatchat_channel_prefix% — kept empty since racked.ru displays
|
||||
# NO channel tag (single channel + already-prefixed LP rank handles all visual distinction).
|
||||
channel-prefix: ''
|
||||
formats:
|
||||
# Single format = single rank-tier visual. Per-recipient rendering means
|
||||
# PAPI %luckperms_prefix% is resolved against the SENDER (the bug-fix vs Carbon).
|
||||
# Kyorifier converts the legacy &-codes from LP prefixes into MiniMessage at render time.
|
||||
default-channel:
|
||||
priority: 1
|
||||
parts:
|
||||
# Carbon original: `<luckperms_prefix><white><display_name></white> <luckperms_suffix><dark_gray>—</dark_gray> <gray><message></gray>`
|
||||
# ChatChat split:
|
||||
prefix:
|
||||
# %luckperms_prefix% returns "&8[&4⛧&8]&r " etc. — Kyorifier handles & → MM.
|
||||
- '%luckperms_prefix%'
|
||||
name:
|
||||
# EssentialsX nicknames flow through %player_displayname%; matches Carbon's <display_name>.
|
||||
- '<white>%player_displayname%</white>'
|
||||
suffix:
|
||||
# No LP suffixes are configured today (lp export shows none) — placeholder kept
|
||||
# so future suffix tiers light up without a config touch.
|
||||
- '%luckperms_suffix%'
|
||||
divider:
|
||||
- ' <dark_gray>—</dark_gray> '
|
||||
message:
|
||||
- '<gray><message></gray>'
|
||||
# Carbon had radius=-1 (server-wide). Match.
|
||||
radius: -1
|
||||
27
staging/chatchat/configs/extensions.yml
Normal file
27
staging/chatchat/configs/extensions.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# racked.ru — ChatChat extensions.yml
|
||||
# Translated 2026-05-07 from /data/plugins/CarbonChat/config.conf integrations block.
|
||||
|
||||
addons:
|
||||
deluxechat:
|
||||
inverse_priorities: false
|
||||
# racked.ru: only chatchat.utf-perm players send non-ASCII. Default-group does NOT
|
||||
# have it (matches Carbon's optional-chat-filter posture). Cyrillic users / russophones
|
||||
# get a per-rank grant on request (handled outside this file via LP, NOT here).
|
||||
unicode_permission:
|
||||
public_chat: true
|
||||
private_chat: true
|
||||
# Towny not installed.
|
||||
towny:
|
||||
channels: false
|
||||
# DiscordSRV not currently loaded (Carbon had it=true but plugin absent). Keep DSRV bridge
|
||||
# disabled here; flip to `true` if/when DSRV is re-added AND its DSRV-config channel name
|
||||
# equals 'global' (NOT the Discord channel name).
|
||||
discordsrv:
|
||||
channels_bridging: false
|
||||
essentials:
|
||||
# Honour Essentials /vanish state — vanished players don't appear as recipients.
|
||||
vanish: true
|
||||
supervanish:
|
||||
vanish: false
|
||||
griefprevention:
|
||||
soft_mute: false
|
||||
34
staging/chatchat/configs/formats.yml
Normal file
34
staging/chatchat/configs/formats.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# racked.ru — ChatChat formats.yml
|
||||
# Channel-defined formats in channels.yml take priority. This file holds the
|
||||
# fallback `default` format (used if a player uses a non-channel context like
|
||||
# legacy plugin-routed chat) AND the console-format (visible in server log).
|
||||
# Translated 2026-05-07.
|
||||
|
||||
# Default format if no channel format matches (defensive — global channel format wins in practice).
|
||||
default-format: 'default'
|
||||
|
||||
# Console / server-log format. Strip MM tags + use LP plain-text prefix so the log
|
||||
# stays readable ASCII (Carbon's console format was: "[<channel>] <username>: <message>").
|
||||
console-format:
|
||||
parts:
|
||||
name:
|
||||
- '%luckperms_prefix_element%%player_name%'
|
||||
divider:
|
||||
- ' — '
|
||||
message:
|
||||
- '<message>'
|
||||
|
||||
formats:
|
||||
default:
|
||||
priority: 2
|
||||
parts:
|
||||
prefix:
|
||||
- '%luckperms_prefix%'
|
||||
name:
|
||||
- '<white>%player_displayname%</white>'
|
||||
suffix:
|
||||
- '%luckperms_suffix%'
|
||||
divider:
|
||||
- ' <dark_gray>—</dark_gray> '
|
||||
message:
|
||||
- '<gray><message></gray>'
|
||||
74
staging/chatchat/configs/settings.yml
Normal file
74
staging/chatchat/configs/settings.yml
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# racked.ru — ChatChat settings.yml
|
||||
# Translated 2026-05-07 from /data/plugins/CarbonChat/config.conf
|
||||
|
||||
# Private messages — operator's canonical command is /pm (s8n exec lock).
|
||||
# ChatChat ships /msg + /reply. Aliases for /pm get added via plugin.yml override
|
||||
# OR via Essentials' command alias system. We keep ChatChat's defaults here and
|
||||
# wire /pm as an alias in commands.yml (Bukkit-level).
|
||||
private-messages:
|
||||
enabled: true
|
||||
formats:
|
||||
sender-format:
|
||||
parts:
|
||||
sender:
|
||||
- '<gray>you'
|
||||
separator:
|
||||
- ' <color:#40c9ff>-> '
|
||||
recipient:
|
||||
# PAPI relational: shows recipient's LP prefix on sender's screen — fine, it's
|
||||
# the canonical "to whom" display.
|
||||
- '%luckperms_prefix%<gray><recipient:player_name></gray>'
|
||||
message:
|
||||
- ' <#e81cff>» <white><message>'
|
||||
recipient-format:
|
||||
parts:
|
||||
sender:
|
||||
- '%luckperms_prefix%<gray>%player_name%</gray>'
|
||||
separator:
|
||||
- ' <#40c9ff>-> '
|
||||
recipient:
|
||||
- '<gray>you'
|
||||
message:
|
||||
- ' <#e81cff>» <white><message>'
|
||||
social-spy-format:
|
||||
parts:
|
||||
prefix:
|
||||
- '<gray>(spy) '
|
||||
sender:
|
||||
- '%player_name%'
|
||||
separator:
|
||||
- ' <#40c9ff>-> '
|
||||
recipient:
|
||||
- '<gray><recipient:player_name>'
|
||||
message:
|
||||
- ' <#e81cff>» <white><message>'
|
||||
|
||||
# Carbon had no equivalent inline-item placeholder enabled by default; ChatChat does <item>.
|
||||
# YOU500 wishlist: gate behind chatchat.tag.item perm grant (added in lp-migration.sh).
|
||||
item-format: '<gray>[</gray><item><gray> x <amount>]'
|
||||
item-format-info: '<dark_gray><item> x <amount>'
|
||||
|
||||
# Mentions — matches Carbon's ping-settings (prefix=@, sound disabled).
|
||||
mentions:
|
||||
prefix: '@'
|
||||
private-message: false
|
||||
sound:
|
||||
name: entity.experience_orb.pickup
|
||||
source: master
|
||||
pitch: 1
|
||||
volume: 1
|
||||
personal-format:
|
||||
parts:
|
||||
name:
|
||||
- '<hover:show_text:"<gold>You were mentioned!">'
|
||||
- '<yellow>@%player_name%'
|
||||
- '</hover>'
|
||||
channel-format:
|
||||
parts:
|
||||
name:
|
||||
- '<hover:show_text:"<gold>Channel mention">'
|
||||
- '<yellow>@everyone'
|
||||
- '</hover>'
|
||||
|
||||
# Carbon had no /reply expiry; ChatChat default 300s is reasonable.
|
||||
last-messaged-cache-duration: 300
|
||||
61
staging/chatchat/scripts/lp-migration.sh
Executable file
61
staging/chatchat/scripts/lp-migration.sh
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env bash
|
||||
# lp-migration.sh — Carbon → ChatChat permission migration
|
||||
# racked.ru / 2026-05-07
|
||||
#
|
||||
# Run from any host with SSH access to nullstone (192.168.0.100). Idempotent —
|
||||
# safe to re-run if a step half-completes. Each command is dispatched via RCON
|
||||
# so LuckPerms picks it up live without restart.
|
||||
#
|
||||
# DOES NOT touch LP rank prefixes/suffixes (locked per CLAUDE.md feedback rule).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SSH_TARGET="${SSH_TARGET:-user@192.168.0.100}"
|
||||
|
||||
rcon() {
|
||||
local cmd="$1"
|
||||
echo ">>> $cmd"
|
||||
ssh "$SSH_TARGET" "docker exec minecraft-mc rcon-cli '$cmd'" || {
|
||||
echo "!!! command failed: $cmd" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
echo "=== ChatChat permission migration — default group ==="
|
||||
|
||||
# 1. Revoke Carbon perms on default group (Carbon JAR will be removed; perms become dead nodes
|
||||
# if left behind, harmless but cleaner to drop).
|
||||
rcon "lp group default permission unset carbon.channel.global.see"
|
||||
rcon "lp group default permission unset carbon.channel.global.speak"
|
||||
|
||||
# 2. Grant ChatChat equivalents.
|
||||
# Channel-level: lets every default-group player SEE and TALK in `global`.
|
||||
rcon "lp group default permission set chatchat.channel.see.global true"
|
||||
rcon "lp group default permission set chatchat.channel.use.global true"
|
||||
|
||||
# 3. Grant private-message + reply (Carbon had no perm gate; ChatChat /msg is OP by default).
|
||||
rcon "lp group default permission set chatchat.pm true"
|
||||
rcon "lp group default permission set chatchat.pm.toggle true"
|
||||
|
||||
# 4. Grant ignore + ignore-list (everyone should be able to mute another player, like Carbon).
|
||||
rcon "lp group default permission set chatchat.ignore true"
|
||||
rcon "lp group default permission set chatchat.ignorelist true"
|
||||
|
||||
# 5. Grant personal mentions (Carbon allowed @-pings; channel-mention `@everyone` stays OP-only).
|
||||
rcon "lp group default permission set chatchat.mention.personal true"
|
||||
rcon "lp group default permission set chatchat.mention.personal.block true"
|
||||
rcon "lp group default permission set chatchat.mention.everyone.block true"
|
||||
|
||||
# 6. Grant the safe MM tags everyone can already use in Carbon (color, hover, item).
|
||||
# NOTE: chatchat.tag.color = uses <color> tags; restrict if abuse appears.
|
||||
rcon "lp group default permission set chatchat.tag.color true"
|
||||
rcon "lp group default permission set chatchat.tag.hover true"
|
||||
rcon "lp group default permission set chatchat.tag.item true"
|
||||
|
||||
echo ""
|
||||
echo "=== verify (will show 'true' for each) ==="
|
||||
rcon "lp group default permission check chatchat.channel.use.global"
|
||||
rcon "lp group default permission check chatchat.pm"
|
||||
|
||||
echo ""
|
||||
echo "=== done. Restart ChatChat (or whole server) so plugin re-evaluates perms. ==="
|
||||
45
staging/chatchat/scripts/lp-rollback.sh
Executable file
45
staging/chatchat/scripts/lp-rollback.sh
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env bash
|
||||
# lp-rollback.sh — undo lp-migration.sh, restore Carbon perms.
|
||||
# racked.ru / 2026-05-07
|
||||
#
|
||||
# Run if the swap window aborts and Carbon JAR is restored.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SSH_TARGET="${SSH_TARGET:-user@192.168.0.100}"
|
||||
|
||||
rcon() {
|
||||
local cmd="$1"
|
||||
echo ">>> $cmd"
|
||||
ssh "$SSH_TARGET" "docker exec minecraft-mc rcon-cli '$cmd'" || {
|
||||
echo "!!! command failed: $cmd" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
echo "=== Rolling back ChatChat perms, restoring Carbon perms ==="
|
||||
|
||||
# 1. Re-grant Carbon perms.
|
||||
rcon "lp group default permission set carbon.channel.global.see true"
|
||||
rcon "lp group default permission set carbon.channel.global.speak true"
|
||||
|
||||
# 2. Drop ChatChat perms (harmless if dead but tidier).
|
||||
rcon "lp group default permission unset chatchat.channel.see.global"
|
||||
rcon "lp group default permission unset chatchat.channel.use.global"
|
||||
rcon "lp group default permission unset chatchat.pm"
|
||||
rcon "lp group default permission unset chatchat.pm.toggle"
|
||||
rcon "lp group default permission unset chatchat.ignore"
|
||||
rcon "lp group default permission unset chatchat.ignorelist"
|
||||
rcon "lp group default permission unset chatchat.mention.personal"
|
||||
rcon "lp group default permission unset chatchat.mention.personal.block"
|
||||
rcon "lp group default permission unset chatchat.mention.everyone.block"
|
||||
rcon "lp group default permission unset chatchat.tag.color"
|
||||
rcon "lp group default permission unset chatchat.tag.hover"
|
||||
rcon "lp group default permission unset chatchat.tag.item"
|
||||
|
||||
echo ""
|
||||
echo "=== verify ==="
|
||||
rcon "lp group default permission check carbon.channel.global.speak"
|
||||
|
||||
echo ""
|
||||
echo "=== rollback done. Restart server so Carbon picks up perms cleanly. ==="
|
||||
50
staging/chatchat/scripts/rollback.sh
Executable file
50
staging/chatchat/scripts/rollback.sh
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
# rollback.sh — undo the swap, restore Carbon.
|
||||
# racked.ru / 2026-05-07
|
||||
#
|
||||
# Use ONLY if smoke test fails after a swap. Looks up the most recent backup dir
|
||||
# created by swap.sh and restores Carbon JAR + configs.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SSH_TARGET="${SSH_TARGET:-user@192.168.0.100}"
|
||||
CONTAINER="${CONTAINER:-minecraft-mc}"
|
||||
COMPOSE_DIR="${COMPOSE_DIR:-/opt/docker/minecraft}"
|
||||
STAGING_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
echo "=== STEP 1: locate the most recent chat-swap backup ==="
|
||||
LATEST=$(ssh "$SSH_TARGET" "docker exec $CONTAINER ls -1 /data/backups | grep ^chat-swap- | sort | tail -1")
|
||||
echo " Restoring from: /data/backups/$LATEST"
|
||||
test -n "$LATEST" || { echo "no backup found, aborting"; exit 1; }
|
||||
|
||||
echo ""
|
||||
echo "=== STEP 2: stop server ==="
|
||||
ssh "$SSH_TARGET" "cd $COMPOSE_DIR && docker compose stop mc"
|
||||
sleep 3
|
||||
|
||||
echo ""
|
||||
echo "=== STEP 3: restore Carbon JAR + dir, remove ChatChat ==="
|
||||
ssh "$SSH_TARGET" "docker run --rm -v minecraft_mc-data:/data alpine:3 sh -c '
|
||||
rm -f /data/plugins/ChatChat-*.jar &&
|
||||
rm -rf /data/plugins/ChatChat &&
|
||||
cp /data/backups/$LATEST/carbonchat-paper-3.0.0-beta.36.jar /data/plugins/ &&
|
||||
cp -r /data/backups/$LATEST/CarbonChat /data/plugins/CarbonChat &&
|
||||
ls -la /data/plugins | grep -iE \"carbon|chat\"
|
||||
'"
|
||||
|
||||
echo ""
|
||||
echo "=== STEP 4: roll back LP perms ==="
|
||||
"${STAGING_DIR}/scripts/lp-rollback.sh" || echo " (LP rollback partially failed — server is down. Will re-try after boot.)"
|
||||
|
||||
echo ""
|
||||
echo "=== STEP 5: start server ==="
|
||||
ssh "$SSH_TARGET" "cd $COMPOSE_DIR && docker compose start mc"
|
||||
sleep 30
|
||||
ssh "$SSH_TARGET" "docker exec $CONTAINER rcon-cli list"
|
||||
|
||||
echo ""
|
||||
echo "=== STEP 6: re-run LP rollback to ensure clean state ==="
|
||||
"${STAGING_DIR}/scripts/lp-rollback.sh"
|
||||
|
||||
echo ""
|
||||
echo "=== rollback complete. Carbon should be live again. ==="
|
||||
102
staging/chatchat/scripts/swap.sh
Executable file
102
staging/chatchat/scripts/swap.sh
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env bash
|
||||
# swap.sh — Carbon → ChatChat plugin swap orchestrator.
|
||||
# racked.ru / 2026-05-07
|
||||
#
|
||||
# Operator-run only. Walks all swap-window steps with confirmation gates.
|
||||
# Paper does NOT support hot-unload of plugins reliably, so this script does a
|
||||
# full server stop → swap → start cycle. Estimated downtime: 60-90s.
|
||||
#
|
||||
# Required env / defaults:
|
||||
# SSH_TARGET=user@192.168.0.100 # nullstone
|
||||
# CONTAINER=minecraft-mc
|
||||
# COMPOSE_DIR=/opt/docker/minecraft
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SSH_TARGET="${SSH_TARGET:-user@192.168.0.100}"
|
||||
CONTAINER="${CONTAINER:-minecraft-mc}"
|
||||
COMPOSE_DIR="${COMPOSE_DIR:-/opt/docker/minecraft}"
|
||||
STAGING_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
JAR_LOCAL="${STAGING_DIR}/build/ChatChat-1.0.0-SNAPSHOT-racked-1.jar"
|
||||
TIMESTAMP="$(date +%Y-%m-%d-%H%M%S)"
|
||||
|
||||
confirm() {
|
||||
read -rp " → press ENTER to continue, ctrl-c to abort: " _
|
||||
}
|
||||
|
||||
step() { echo ""; echo "=== STEP $1: $2 ==="; }
|
||||
|
||||
step 1 "Pre-flight checks"
|
||||
test -f "$JAR_LOCAL" || { echo "MISSING: $JAR_LOCAL — run build/build.sh first"; exit 1; }
|
||||
test -d "${STAGING_DIR}/configs" || { echo "MISSING configs dir"; exit 1; }
|
||||
ssh "$SSH_TARGET" "docker exec $CONTAINER rcon-cli list" >/dev/null
|
||||
echo " Server reachable; jar found ($(du -h "$JAR_LOCAL" | cut -f1))."
|
||||
|
||||
step 2 "Broadcast swap warning to players (60s lead)"
|
||||
ssh "$SSH_TARGET" "docker exec $CONTAINER rcon-cli 'say [racked.ru] chat plugin swap in 60s — restart incoming, world is safe'"
|
||||
echo " Sleeping 60s..."
|
||||
sleep 60
|
||||
ssh "$SSH_TARGET" "docker exec $CONTAINER rcon-cli 'save-all'"
|
||||
sleep 3
|
||||
|
||||
step 3 "Backup current Carbon plugin + configs on nullstone"
|
||||
ssh "$SSH_TARGET" "docker exec $CONTAINER bash -c '
|
||||
mkdir -p /data/backups/chat-swap-${TIMESTAMP} &&
|
||||
cp /data/plugins/carbonchat-paper-3.0.0-beta.36.jar /data/backups/chat-swap-${TIMESTAMP}/ &&
|
||||
cp -r /data/plugins/CarbonChat /data/backups/chat-swap-${TIMESTAMP}/ &&
|
||||
ls -la /data/backups/chat-swap-${TIMESTAMP}/
|
||||
'"
|
||||
echo " Backup OK."
|
||||
confirm
|
||||
|
||||
step 4 "Stop the server (compose down preserves volumes)"
|
||||
ssh "$SSH_TARGET" "cd $COMPOSE_DIR && docker compose stop mc"
|
||||
echo " Stopped. Verify container is down before continuing."
|
||||
ssh "$SSH_TARGET" "docker ps -a --filter name=$CONTAINER --format '{{.Names}} {{.Status}}'"
|
||||
|
||||
step 5 "Remove Carbon JAR + dir, drop ChatChat JAR + configs"
|
||||
# Carbon
|
||||
ssh "$SSH_TARGET" "docker run --rm -v minecraft_mc-data:/data alpine:3 sh -c '
|
||||
rm -f /data/plugins/carbonchat-paper-3.0.0-beta.36.jar &&
|
||||
mv /data/plugins/CarbonChat /data/plugins/CarbonChat.disabled-${TIMESTAMP}
|
||||
'"
|
||||
# ChatChat jar
|
||||
scp "$JAR_LOCAL" "$SSH_TARGET":/tmp/chatchat.jar
|
||||
ssh "$SSH_TARGET" "docker run --rm -v /tmp/chatchat.jar:/in.jar:ro -v minecraft_mc-data:/data alpine:3 sh -c '
|
||||
cp /in.jar /data/plugins/ChatChat-1.0.0-SNAPSHOT-racked-1.jar &&
|
||||
mkdir -p /data/plugins/ChatChat
|
||||
'"
|
||||
# ChatChat configs
|
||||
for f in channels.yml formats.yml settings.yml extensions.yml; do
|
||||
scp "${STAGING_DIR}/configs/${f}" "$SSH_TARGET":/tmp/${f}
|
||||
ssh "$SSH_TARGET" "docker run --rm -v /tmp/${f}:/in.yml:ro -v minecraft_mc-data:/data alpine:3 sh -c 'cp /in.yml /data/plugins/ChatChat/${f}'"
|
||||
done
|
||||
ssh "$SSH_TARGET" "rm -f /tmp/chatchat.jar /tmp/channels.yml /tmp/formats.yml /tmp/settings.yml /tmp/extensions.yml"
|
||||
echo " Plugins/configs swapped."
|
||||
confirm
|
||||
|
||||
step 6 "Apply LP permission migration"
|
||||
"${STAGING_DIR}/scripts/lp-migration.sh"
|
||||
echo " NOTE: LP perms above failed because server is offline — that's expected."
|
||||
echo " Will re-run after server boot."
|
||||
confirm
|
||||
|
||||
step 7 "Start the server"
|
||||
ssh "$SSH_TARGET" "cd $COMPOSE_DIR && docker compose start mc"
|
||||
echo " Waiting 30s for full boot..."
|
||||
sleep 30
|
||||
ssh "$SSH_TARGET" "docker exec $CONTAINER rcon-cli list"
|
||||
|
||||
step 8 "Re-apply LP permission migration (now that RCON is live)"
|
||||
"${STAGING_DIR}/scripts/lp-migration.sh"
|
||||
|
||||
step 9 "Smoke test — operator-driven"
|
||||
echo ""
|
||||
echo " Run these manually on the server:"
|
||||
echo " 1. Log in as s8n; check chat from another client (or YOU500's perspective)."
|
||||
echo " 2. /msg s8n hi (should work for default-group player)"
|
||||
echo " 3. Have YOU500 chat — confirm prefix shown is [Adventurer] (his rank), NOT [⛧] (s8n's)."
|
||||
echo " 4. Check console-log: each chat line should show LP plain prefix."
|
||||
echo ""
|
||||
echo " If smoke passes: leave it running."
|
||||
echo " If smoke FAILS: run scripts/rollback.sh"
|
||||
Loading…
Reference in a new issue