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:
s8n 2026-05-07 22:23:11 +01:00
parent 188f43d308
commit 41ae6f90ef
12 changed files with 680 additions and 0 deletions

3
.gitignore vendored
View file

@ -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

View 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): **4560 seconds**.
- Worst case (config syntax error forces second restart, or world I/O slow): **35 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 (4590s).
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
```

View 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
View 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

View 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

View 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

View 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>'

View 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

View 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. ==="

View 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. ==="

View 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
View 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"