diff --git a/docs/ITZG-CUSTOM-JAR-PERSISTENCE.md b/docs/ITZG-CUSTOM-JAR-PERSISTENCE.md new file mode 100644 index 0000000..7fdf42a --- /dev/null +++ b/docs/ITZG-CUSTOM-JAR-PERSISTENCE.md @@ -0,0 +1,115 @@ +# itzg/minecraft-server — Custom Plugin Jar Persistence + +**Date:** 2026-05-07 +**Context:** `ChatChat-1.0.0-SNAPSHOT-racked-1.jar` was dropped manually into `/data/plugins/` and disappeared after the next container restart. + +--- + +## 1. Why the ChatChat jar disappeared + +itzg's entrypoint runs this sequence on every start (when `REMOVE_OLD_MODS=TRUE`): + +1. **Wipe** — every file in `/data/plugins/` matching `REMOVE_OLD_MODS_INCLUDE` and not matching `REMOVE_OLD_MODS_EXCLUDE` is deleted. +2. **Download** — every URL in `PLUGINS` and every Modrinth project in `MODRINTH_PROJECTS` is fetched into `/data/plugins/`. +3. **Copy** — anything listed in `COPY_PLUGINS_SRC` (or container paths inside `PLUGINS`) is rsynced in. + +Our current compose has: + +```yaml +REMOVE_OLD_MODS: "true" +REMOVE_OLD_MODS_INCLUDE: "*.jar" +REMOVE_OLD_MODS_EXCLUDE: "AuthLimbo*.jar" +``` + +So step 1 deletes **every** `*.jar` except `AuthLimbo*.jar`. The hand-placed ChatChat jar was not in any download list and not in the exclude glob, so it was nuked and never re-downloaded. AuthLimbo survives only because we explicitly excluded it. + +This is documented behaviour, not a bug — itzg's design assumes plugins are declarative, sourced from URLs/Modrinth/`COPY_PLUGINS_SRC`, never hand-dropped. + +## 2. Three mechanisms to make custom jars persist + +| # | Mechanism | How | +|---|-----------|-----| +| A | **`REMOVE_OLD_MODS_EXCLUDE` glob** | Add `ChatChat*.jar` to the exclude list. Quick but fragile — depends on filename and only protects already-present files; doesn't handle re-deploy on a fresh volume. | +| B | **`COPY_PLUGINS_SRC` bind-mount** | Mount a host dir of custom jars read-only at e.g. `/plugins-custom`, set `COPY_PLUGINS_SRC=/plugins-custom`. Entrypoint copies them in after the wipe. Survives wipes, version-controllable, declarative. | +| C | **`PLUGINS` URL → Forgejo Release** | Upload the jar as a Forgejo Release asset, add the download URL to the existing `PLUGINS` list. Same flow as EssentialsX/spark already use. | + +Note: `PLUGINS` also accepts container paths directly (e.g. `PLUGINS=/plugins-custom/ChatChat.jar`), so mechanism B can collapse into the existing `PLUGINS` env if preferred. + +## 3. Recommended path for racked.ru — Mechanism B + +`COPY_PLUGINS_SRC` is the cleanest fit: +- Custom jars live in the repo (or `/opt/docker/minecraft-custom-plugins/`), so they're under version control / backup. +- No external host dependency (Forgejo could be down — bind mount can't be). +- Build artefacts from `staging/chatchat/` drop straight into the mounted dir. + +### docker-compose.yml diff + +```diff + MODRINTH_LOADER: paper + SPIGET_RESOURCES: "" + REMOVE_OLD_MODS: "true" + REMOVE_OLD_MODS_INCLUDE: "*.jar" +- REMOVE_OLD_MODS_EXCLUDE: "AuthLimbo*.jar" ++ REMOVE_OLD_MODS_EXCLUDE: "AuthLimbo*.jar,ChatChat*.jar" ++ COPY_PLUGINS_SRC: "/plugins-custom" + volumes: + - /opt/docker/minecraft:/data ++ - /opt/docker/minecraft-custom-plugins:/plugins-custom:ro +``` + +Then on the host: + +```bash +sudo mkdir -p /opt/docker/minecraft-custom-plugins +sudo cp staging/chatchat/ChatChat-1.0.0-SNAPSHOT-racked-1.jar \ + /opt/docker/minecraft-custom-plugins/ +sudo cp /opt/docker/minecraft/plugins/AuthLimbo-*.jar \ + /opt/docker/minecraft-custom-plugins/ # optional: source-of-truth +sudo chown -R 1000:1000 /opt/docker/minecraft-custom-plugins +docker compose up -d --force-recreate mc +``` + +The `EXCLUDE` line still lists `ChatChat*.jar` so that if the bind mount ever vanishes, an existing copy in `/data/plugins/` isn't wiped — belt and braces. + +## 4. Bonus — Forgejo Release upload procedure (mechanism C) + +If you'd rather host the jar at `git.s8n.ru` (e.g. for cobblestone or a friend's box without the bind mount): + +```bash +# 1. Tag and push +cd staging/chatchat +git tag -a chatchat-racked-1 -m "ChatChat racked build 1" +git push origin chatchat-racked-1 + +# 2. Create release + upload asset (uses Forgejo PAT from ~/.config/veilor-forgejo) +TOKEN=$(cat ~/.config/veilor-forgejo/pat) +curl -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + https://git.s8n.ru/api/v1/repos/s8n/minecraft-server/releases \ + -d '{"tag_name":"chatchat-racked-1","name":"ChatChat racked-1","draft":false}' + +RELEASE_ID=$(curl -s -H "Authorization: token $TOKEN" \ + https://git.s8n.ru/api/v1/repos/s8n/minecraft-server/releases/tags/chatchat-racked-1 \ + | jq -r .id) + +curl -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/java-archive" \ + --data-binary @ChatChat-1.0.0-SNAPSHOT-racked-1.jar \ + "https://git.s8n.ru/api/v1/repos/s8n/minecraft-server/releases/$RELEASE_ID/assets?name=ChatChat-1.0.0-SNAPSHOT-racked-1.jar" +``` + +Then add to compose: + +```yaml +PLUGINS: | + ...existing... + https://git.s8n.ru/s8n/minecraft-server/releases/download/chatchat-racked-1/ChatChat-1.0.0-SNAPSHOT-racked-1.jar +``` + +## References + +- itzg docs: https://docker-minecraft-server.readthedocs.io/en/latest/mods-and-plugins/ +- Source: https://github.com/itzg/docker-minecraft-server/blob/master/docs/mods-and-plugins/index.md +- Issue #310 (COPY_PLUGINS_SRC behaviour): https://github.com/itzg/docker-minecraft-server/issues/310 diff --git a/docs/MIGRATION-PLAN-EXCELLENTSHOP.md b/docs/MIGRATION-PLAN-EXCELLENTSHOP.md new file mode 100644 index 0000000..11e752f --- /dev/null +++ b/docs/MIGRATION-PLAN-EXCELLENTSHOP.md @@ -0,0 +1,137 @@ +# Migration Plan — EZShop + Kiranhart AH → ExcellentShop unified + +**Date:** 2026-05-08 +**Author:** s8n +**Supersedes:** AUDIT-2026-05-07 F-11; SHOP-SYSTEM-DECISION sec. 6 (now operational) +**Staging:** `staging/excellentshop/` +**Status:** Ready for operator-scheduled maintenance window. + +--- + +## 1. Summary + +Replace two plugins (EZShop 1.0-SNAPSHOT bespoke + Kiranhart AuctionHouse +1.4.6 ARR-no-LICENSE) with one GPL-3 stack: +ExcellentShop 5.0.1 + CoinsEngine 2.7.0, both built by NightExpress against +nightcore 2.15.3. All three jars are sourced from the upstream Reposilite +(`repo.nightexpressdev.com`) — source-of-truth identical to a local +`mvn package` from `github.com/nulli0n/ExcellentShop-spigot`, just reproducible +without the brittle `alex9849` integration that fails on TLS handshake. + +Both modules share one theme, one perm tree, one update cadence. Chest Shop +module is disabled — not a future-of-server commit (QuickShop-Hikari path +already documented in SHOP-SYSTEM-DECISION). + +## 2. Pre-flight (not in `swap.sh`, do these first) + +1. **Heads-up window:** post in-game notice via `/pm news` minus 7d, 1d, 1h. + "Auction listings will be drained on YYYY-MM-DD HH:MM." +2. **Drain Kiranhart AH:** instruct players to claim/cancel listings. + Bid-in-progress items revert at the close of the bid window (max 2h, + per `bid-auction-duration: 7200`). Manual refund the residue to the + listing owner via `/eco give` after the window — record the audit + trail in `live-server/migration-2026-05-08-residue.log`. +3. **Snapshot live data:** invoke + `scripts/restic-backup-playerdata.sh` (already wired for nullstone). + Verify the snapshot via `restic snapshots --tag pre-shop-migration`. +4. **Verify staged bundle:** + ``` + cd staging/excellentshop/build + sha256sum *.jar # match the values committed alongside this doc + ``` +5. **Confirm operator window:** compose stop → up takes ~3 minutes; + ExcellentShop generates ~11 default shops + 400 items on first start + (~30s extra). Total expected downtime: 4-6 minutes best case, + 12 minutes worst case (if itzg downloads stall). + +## 3. Swap procedure + +`scripts/swap.sh` runs as `user@nullstone` and performs: + +| Step | Action | +|------|--------| +| 1 | `docker compose stop mc` | +| 2 | tar legacy `EZShop` + `AuctionHouse` plugin trees + jars to `/opt/docker/minecraft/plugins.tar.bak-` | +| 3 | privileged `rm -rf` of legacy trees and jars (uses `docker run --privileged --userns=host` per `feedback_docker_sudo_bypass`) | +| 4 | populate `/opt/docker/minecraft-custom-plugins/` with the three staged jars, chown 1000:1000 | +| 5 | replace `docker-compose.yml` with `docker-compose.patch.yml` (adds `COPY_PLUGINS_SRC=/plugins-custom`, the bind mount, and the new `REMOVE_OLD_MODS_EXCLUDE` glob list) — keeps a `.bak-` | +| 6 | `docker compose up -d mc` (first-start config generation runs here) | +| 7 | poll `mc-health` for up to 4 minutes | +| 8 | `docker cp` the staged config overrides into the now-populated plugin tree, run `excellentshop reload` + `coinsengine reload`, then run `lp-shop-migration.sh` over RCON to grant default-tier perms | + +The compose patch is identical to the existing live compose **except** for +the three lines: `PLUGINS` (drops EZShop+AH URLs), `REMOVE_OLD_MODS_EXCLUDE` +(adds three new globs), and `COPY_PLUGINS_SRC` + matching volume mount. + +## 4. Smoke test (post-swap, manual) + +Run as a non-staff player and as a staff player: + +- `/shop` → main category menu, 11 shops, GUI renders within 1s. +- `/sellall` → sells inventory, `+N coins` chat feedback, `/balance` updates. +- `/sellhand` → sells hand stack, hand cleared. +- `/ah` → AH GUI opens, list a test item via `/ah list 100`, see it in browse. +- `/ah` admin: `/ah admin remove ` works, `/ah admin expire` works. +- `/balance`, `/pay`, `/eco` resolve through CoinsEngine via Vault bridge. +- Tail logs `docker logs -f minecraft-mc | grep -E 'nightcore|ExcellentShop|CoinsEngine|ERROR|WARN'` for 5 minutes after first player joins. + +If any of the above fail, run `scripts/rollback.sh`. + +## 5. Rollback + +`scripts/rollback.sh` reverses everything: stops the container, removes +ExcellentShop+CoinsEngine+nightcore plugin trees and jars, clears the +custom-plugins bind dir, restores legacy `EZShop`+`AuctionHouse` from the +tar snapshot, restores the previous `docker-compose.yml`, restarts, and +runs `lp-shop-rollback.sh` over RCON to restore the legacy perm nodes. + +Rollback total time: ~5 minutes. + +## 6. Risk register + +| # | Risk | Likelihood | Impact | Mitigation | +|---|------|-----------|--------|-----------| +| R1 | nightcore 2.15.3 incompatible with Purpur 1.21.11 (api-version 1.21 should match) | Low | High | Smoke-test on a throwaway container first; rollback path < 5 min if breakage | +| R2 | CoinsEngine 2.7.0 fails Vault bridge (EssentialsX expects Vault) | Medium | High | `Vault_Compatibility.Default: true` set in staged currency YAML; verify `/balance` in smoke step 4 | +| R3 | NightExpress reposilite goes dark before next maintenance | Low | Low | Jars are committed to the repo at `staging/excellentshop/build/` with sha256 receipts — never re-fetched at swap time | +| R4 | Players keep typing `/auctionhouse` (legacy alias) | Certain | Low | `commands.yml` aliases `[ah, auctionhouse, market]` keep muscle memory | +| R5 | EZShop balance migration loss (if EZShop tracked any) | Very Low | Low | EZShop was sell-only with Vault → balances live in EssentialsX, untouched by this swap | +| R6 | Live AH listings lost (residual stuck items) | Medium | Medium | Pre-flight step 2 drains, residue refund is documented | +| R7 | First-start delay > 4 min healthcheck window (large default catalog generation) | Low | Medium | `start_period: 240s` already set; if exceeded, manual `docker exec mc mc-health` poll | +| R8 | Compose typo locks server out of plugins | Low | High | `swap.sh` keeps `docker-compose.yml.bak-` for trivial revert; rollback restores it | +| R9 | `excellentshop.shop.use` not granted to default group → no players can `/shop` | Medium | High | `lp-shop-migration.sh` step 8 grants the full perm tree; verify with `/lp user permission check excellentshop.shop.use` | +| R10 | Compose `cap_drop: ALL` strips a cap CoinsEngine needs | Low | Low | If `/data/plugins/CoinsEngine/` write fails, `cap_add: DAC_OVERRIDE` rescue documented in swap.sh comments | + +## 7. Top 3 swap-window risks (operator brief) + +1. **R9 (perm regression)** — if the LP migration RCON call drops mid-script, + players see `/shop` work but `/sellall` fail. Mitigation: re-run + `lp-shop-migration.sh` idempotently — every line is `permission set true`. +2. **R7 (slow first-start)** — first start of a fresh ExcellentShop with the + bundled catalog generation takes ~30s extra and might trip the 240s + healthcheck on a cold pool. Mitigation: `swap.sh` step 7 polls manually + before declaring success. +3. **R2 (Vault bridge)** — if `/balance` returns 0 instead of the player's + actual balance, the Vault bridge from CoinsEngine is mis-wired. Mitigation: + `Vault_Compatibility.Default: true` is in the staged YAML; if still broken, + `/eco give ` from the EssentialsX-side dump that + `restic-backup-playerdata.sh` already takes. + +## 8. Estimated downtime + +- **Best case:** 4 minutes (compose stop → up + health check, no log churn). +- **Typical:** 6 minutes (one extra stall on jar copy or LP RCON timeout). +- **Worst case:** 12 minutes (first-start catalog generation slow + manual smoke). + +If the worst case is exceeded, abort and run `rollback.sh` — the data +snapshot from pre-flight step 3 is the source of truth. + +## 9. Post-migration + +- Update `AUDIT-2026-05-07.md` F-11: status → Resolved (date 2026-05-08). +- Update `docs/COMMANDS.md`: `/shop`, `/ah`, `/sellall`, `/sellhand`, + `/balance` all now route through ExcellentShop+CoinsEngine. +- Update `SYSTEM.md` plugin table: drop EZShop, drop AuctionHouse 1.4.6, + add ExcellentShop 5.0.1, CoinsEngine 2.7.0, nightcore 2.15.3. +- Schedule a follow-up audit in 7d to confirm no log spam, no economy + drift, no listing exploits. diff --git a/staging/excellentshop/build/CoinsEngine-2.7.0.jar b/staging/excellentshop/build/CoinsEngine-2.7.0.jar new file mode 100644 index 0000000..836c53d Binary files /dev/null and b/staging/excellentshop/build/CoinsEngine-2.7.0.jar differ diff --git a/staging/excellentshop/build/ExcellentShop-5.0.1.jar b/staging/excellentshop/build/ExcellentShop-5.0.1.jar new file mode 100644 index 0000000..1c53546 Binary files /dev/null and b/staging/excellentshop/build/ExcellentShop-5.0.1.jar differ diff --git a/staging/excellentshop/build/SHA256SUMS b/staging/excellentshop/build/SHA256SUMS new file mode 100644 index 0000000..3cb43ed --- /dev/null +++ b/staging/excellentshop/build/SHA256SUMS @@ -0,0 +1,3 @@ +cbb18ee42388acd2b1eac58eacba44db6de6b2f8939981e7e6eb106314db31a2 CoinsEngine-2.7.0.jar +a91cb85af94b77097eaf14cae0058b7b0a0001df2cc4c9ab8d7f8710bdb1ad1e ExcellentShop-5.0.1.jar +b1313e0455c41bc2bad5de962fa80a59ba9ca8a42c6385d7fa3291d7fb0bb803 nightcore-2.15.3.jar diff --git a/staging/excellentshop/build/nightcore-2.15.3.jar b/staging/excellentshop/build/nightcore-2.15.3.jar new file mode 100644 index 0000000..4be38ec Binary files /dev/null and b/staging/excellentshop/build/nightcore-2.15.3.jar differ diff --git a/staging/excellentshop/configs/README.md b/staging/excellentshop/configs/README.md new file mode 100644 index 0000000..7a5a70f --- /dev/null +++ b/staging/excellentshop/configs/README.md @@ -0,0 +1,40 @@ +# ExcellentShop / CoinsEngine Configs (Staged) + +This directory holds **post-first-run** configuration overrides for the migration. + +## How it works + +ExcellentShop and CoinsEngine generate their own opinionated default +configs the first time the plugin starts. Those defaults are excellent +out of the box — 11 themed virtual shops with ~400 vanilla items, sane +buy/sell prices, polished GUIs. + +Rather than ship a hand-translated EZShop catalog (which would be a +strict regression — EZShop only had ~80 items, sell-only), this +migration **adopts the bundled ExcellentShop catalog** and lays down +a small set of **operator-tunable overrides** on top. + +The bundled catalog already covers the eight EZShop categories the +operator listed: building blocks, colored blocks, food, mob drops, +miscellaneous, minerals, redstone, farming, decoration, plus +combat/tools and potions. + +## Files in this dir + +- `apply-overrides.sh` — runs after first start, patches the 11 generated shop YAMLs to apply the racked.ru tweaks listed below. +- `coinsengine/config.yml` — pre-staged, drops in the single `coin` currency named "coins" (matches the EZShop messages). +- `coinsengine/currencies/coin.yml` — currency definition. +- `excellentshop/config.yml` — top-level plugin overrides (modules: virtual=on, auction=on, chest=OFF). +- `excellentshop/modules/virtual_shop/config.yml` — `/shop` alias, sell-all, sell-hand commands, no rank multiplier (we keep parity with EZShop's flat pricing). +- `excellentshop/modules/auction/config.yml` — Kiranhart-equivalent settings: 1% tax, BIN+bid, 10 listings default, 7200s bid duration, 172800s BIN duration. + +## What's NOT staged + +- Per-item prices — bundled defaults are kept. If the operator wants to align specific items to legacy EZShop prices, edit the generated `plugins/ExcellentShop/modules/virtual_shop/shops//config.yml` files and adjust `sell` / `sellAll` price fields. EZShop legacy table preserved at `legacy-ezshop-prices.yml` for reference. +- `legacy-ezshop-prices.yml` — full export of EZShop's `item-prices` map; reference only. + +## Layout reference (54-slot per category from YOU500 ask) + +Bundled shops are 54-slot (6 rows) by default with category icons in the +main menu, exactly the layout described in `SHOP-SYSTEM-DECISION.md`. +The operator does NOT need to manually configure 54-slot — it's the default. diff --git a/staging/excellentshop/configs/coinsengine/config.yml b/staging/excellentshop/configs/coinsengine/config.yml new file mode 100644 index 0000000..2d61ca0 --- /dev/null +++ b/staging/excellentshop/configs/coinsengine/config.yml @@ -0,0 +1,13 @@ +# CoinsEngine top-level config (racked.ru) +# CoinsEngine handles currency for ExcellentShop. We define a single +# currency named "coins" (matches EZShop's existing "coins" message tokens). + +General: + Save_Interval: 600 + Currency_Need_Permission: false + Format: + # Match EZShop legacy: 'Sold for &f%amount% &7coins.' + Suffix: " coins" + Prefix: "" + Decimal_Places: 2 + Use_Short_Suffixes: true # 1.5K / 2.3M for big numbers in AH listings diff --git a/staging/excellentshop/configs/coinsengine/currencies/coin.yml b/staging/excellentshop/configs/coinsengine/currencies/coin.yml new file mode 100644 index 0000000..d4ae44b --- /dev/null +++ b/staging/excellentshop/configs/coinsengine/currencies/coin.yml @@ -0,0 +1,24 @@ +# Single currency for racked.ru — "coins" +# Matches the in-game currency name players already see in EZShop messages. + +Settings: + Enabled: true + Name: "&fcoins" + Symbol: "c" + Decimal: false # whole-number coins (vanilla feel) + Default_Balance: 0 + Starting_Balance: 0 + Max_Balance: -1 # no cap + Min_Balance: 0 # no overdraft + +Vault_Compatibility: + Default: true # our /eco, /pay, /balance route through CoinsEngine + Currency_Name_Singular: coin + Currency_Name_Plural: coins + +Commands: + # /balance, /pay — keep parity with EssentialsX surface + Aliases: [coins] + Pay: + Enabled: true + Tax_Percent: 0.0 diff --git a/staging/excellentshop/configs/excellentshop/config.yml b/staging/excellentshop/configs/excellentshop/config.yml new file mode 100644 index 0000000..f3146d8 --- /dev/null +++ b/staging/excellentshop/configs/excellentshop/config.yml @@ -0,0 +1,19 @@ +# ExcellentShop top-level config (racked.ru post-migration override) +# Drop into /data/plugins/ExcellentShop/config.yml AFTER first start has +# populated the file, then `/excellentshop reload`. + +General: + Auto_Save_Logs: false # we ship our own log rotation via itzg + Currency_Need_Permission: false + Buy_With_Full_Inventory: false + +Modules: + # Disable Chest Shop module — racked.ru does not expose chest shops + # (QuickShop-Hikari is the future story; for now /shop + /ah are the + # canonical commands per the SHOP-SYSTEM-DECISION). + ChestShop: + Enabled: false + VirtualShop: + Enabled: true + Auction: + Enabled: true diff --git a/staging/excellentshop/configs/excellentshop/modules/auction/config.yml b/staging/excellentshop/configs/excellentshop/modules/auction/config.yml new file mode 100644 index 0000000..9853a42 --- /dev/null +++ b/staging/excellentshop/configs/excellentshop/modules/auction/config.yml @@ -0,0 +1,38 @@ +# ExcellentShop / Auction House module — racked.ru overrides +# Translates the Kiranhart 1.4.6 settings (tax 1%, BIN+bid, 10 listings) +# into ExcellentShop AH schema. Apply AFTER first start. + +General: + # 1% tax on listing creation — matches Kiranhart 'tax: 0.01' + Listing_Tax: 0.01 + Per_Player_Listing_Limit: 10 # matches Kiranhart 'default-max-auctions' + Sold_Notification: true # matches 'sold-message: true' + Auto_Collect_Sold: false # matches 'auto-collect: false' + Partial_Selling: false # matches 'partial-selling: false' + Announce_Listings: true # matches 'auction-announcements: true' + +Commands: + # Aliases — keep current /ah and /auctionhouse muscle memory + Auction: [ah, auctionhouse, market] + Sell: [ahsell, ahlist] + +Listings: + # BIN (Buy-It-Now) — matches Kiranhart 'bin-auctions: true' + BIN: + Enabled: true + Default_Duration: 172800 # 48h, matches 'bin-auction-duration' + Min_Price: 1 # matches 'min-bin: 1' + Max_Price: -1 # matches 'max-bin: -1' (no cap) + # Bidding — matches Kiranhart 'bid-auctions: true' + Bid: + Enabled: true + Default_Duration: 7200 # 2h, matches 'bid-auction-duration' + Min_Start: 1 # matches 'min-bid: 1' + Max_Start: -1 # matches 'max-bid: -1' + Last_Bid_Extra_Time: 60 # matches 'last-bid-extra-time' + Bid_Increase_Percent: 25 # matches 'bid-increase-percent' + +Permissions: + # 'auctionhouse.moderator' under Kiranhart -> 'excellentshop.auction.admin' here + # mapped via lp-shop-migration.sh. + Admin_Node: excellentshop.auction.admin diff --git a/staging/excellentshop/configs/excellentshop/modules/virtual_shop/config.yml b/staging/excellentshop/configs/excellentshop/modules/virtual_shop/config.yml new file mode 100644 index 0000000..d25af25 --- /dev/null +++ b/staging/excellentshop/configs/excellentshop/modules/virtual_shop/config.yml @@ -0,0 +1,30 @@ +# ExcellentShop / Virtual Shop module — racked.ru overrides +# Apply AFTER first start. + +General: + Save_Interval: 600 + Default_Layout: "default" + Main_Menu: + Enabled: true + Hide_No_Permission_Shops: false + Shop_Shortcut: + Enabled: true + # /shop opens the main category menu (replaces EZShop /shop) + Commands: [shop, vshop] + Sell_Menu: + Enabled: true + Commands: [sell] + Sell_All: + Enabeled: true # sic — typo is in the upstream config schema + Commands: [sellall] + Sell_Hand: + Enabled: true + Commands: [sellhand] + Sell_Hand_All: + Enabled: true + Commands: [sellhandall] + # No rank multiplier — racked.ru uses flat pricing (LP prefixes are locked, + # discounts are out of scope, see feedback_lp_prefixes_locked.md) + Sell_Multipliers: {} + Disabled_In_Gamemodes: [CREATIVE, SPECTATOR] + Disabled_In_Worlds: [auth_limbo, void_lobby] diff --git a/staging/excellentshop/configs/legacy-ezshop-prices.yml b/staging/excellentshop/configs/legacy-ezshop-prices.yml new file mode 100644 index 0000000..5dce652 --- /dev/null +++ b/staging/excellentshop/configs/legacy-ezshop-prices.yml @@ -0,0 +1,110 @@ +# racked.ru sell-shop +# Players /shop, drop items, click sell. Vanilla items only. +# Prices balanced for survival economy. Tune as needed. + +item-prices: + # mining + COBBLESTONE: 1 + STONE: 2 + ANDESITE: 1 + GRANITE: 1 + DIORITE: 1 + GRAVEL: 2 + SAND: 2 + RED_SAND: 2 + CLAY_BALL: 3 + COAL: 4 + IRON_INGOT: 10 + COPPER_INGOT: 6 + GOLD_INGOT: 40 + DIAMOND: 100 + EMERALD: 35 + LAPIS_LAZULI: 5 + REDSTONE: 3 + QUARTZ: 6 + NETHERITE_INGOT: 1000 + AMETHYST_SHARD: 8 + GLOWSTONE_DUST: 4 + OBSIDIAN: 8 + + # wood + OAK_LOG: 3 + BIRCH_LOG: 3 + SPRUCE_LOG: 3 + JUNGLE_LOG: 3 + ACACIA_LOG: 3 + DARK_OAK_LOG: 3 + MANGROVE_LOG: 4 + CHERRY_LOG: 4 + CRIMSON_STEM: 5 + WARPED_STEM: 5 + + # crops + WHEAT: 2 + CARROT: 2 + POTATO: 2 + BEETROOT: 2 + PUMPKIN: 3 + MELON_SLICE: 1 + SUGAR_CANE: 2 + COCOA_BEANS: 3 + KELP: 1 + BAMBOO: 1 + SWEET_BERRIES: 2 + GLOW_BERRIES: 2 + + # food + APPLE: 4 + BREAD: 5 + GOLDEN_APPLE: 80 + GOLDEN_CARROT: 12 + COOKED_BEEF: 5 + COOKED_PORKCHOP: 5 + COOKED_CHICKEN: 5 + COOKED_MUTTON: 5 + COOKED_RABBIT: 5 + COOKED_SALMON: 5 + COOKED_COD: 5 + COOKIE: 3 + CAKE: 15 + HONEY_BOTTLE: 8 + + # mob drops + ROTTEN_FLESH: 1 + BONE: 3 + STRING: 4 + SPIDER_EYE: 6 + GUNPOWDER: 8 + ENDER_PEARL: 25 + BLAZE_ROD: 18 + GHAST_TEAR: 30 + SLIME_BALL: 5 + MAGMA_CREAM: 10 + PHANTOM_MEMBRANE: 15 + SHULKER_SHELL: 100 + NETHER_STAR: 2000 + WITHER_ROSE: 50 + HEART_OF_THE_SEA: 500 + TOTEM_OF_UNDYING: 600 + + # nether + NETHERRACK: 1 + SOUL_SAND: 2 + SOUL_SOIL: 2 + GLOWSTONE: 5 + NETHER_BRICK: 2 + ANCIENT_DEBRIS: 200 + + # ice / snow / misc + ICE: 2 + SNOWBALL: 1 + MUSHROOM: 2 + RED_MUSHROOM: 2 + BROWN_MUSHROOM: 2 + +messages: + sellSuccess: "&7[&fShop&7] &fSold for &f%amount% &7coins." + invalidItems: "&7[&fShop&7] &fNo sellable items." + guiTitle: "Shop" + itemLore1: "&fSell" + itemLore2: "&7Click to sell your items." diff --git a/staging/excellentshop/scripts/docker-compose.patch.yml b/staging/excellentshop/scripts/docker-compose.patch.yml new file mode 100644 index 0000000..ffa013f --- /dev/null +++ b/staging/excellentshop/scripts/docker-compose.patch.yml @@ -0,0 +1,116 @@ +services: + mc: + image: itzg/minecraft-server:latest + container_name: minecraft-mc + environment: + EULA: "TRUE" + # TYPE=CUSTOM bypasses itzg's auto-loader detection; honors MODRINTH_LOADER override + TYPE: CUSTOM + CUSTOM_SERVER: "https://api.purpurmc.org/v2/purpur/1.21.11/latest/download" + VERSION: "1.21.11" + + # H3 (2026-05-07): Xmx lowered 16384M -> 14336M to leave ~3.5G headroom + # for off-heap (Netty buffers, native mmaps, plugin metadata) inside the + # 18G container limit. See AUDIT-2026-05-07.md F-05. + MEMORY_SIZE: "14G" + JVM_OPTS: "-Xms8192M -Xmx14336M" + + DIFFICULTY: hard + GAMEMODE: survival + HARDCORE: "false" + MAX_PLAYERS: "69" + VIEW_DISTANCE: 20 + SIMULATION_DISTANCE: 10 + ONLINE_MODE: "false" + SPAWN_PROTECTION: 0 + MOTD: "racked.ru — experience the game the way it should have always been" + + USE_AIKAR_FLAGS: "true" + MAX_TICK_TIME: "-1" + + ENABLE_RCON: "true" + RCON_PASSWORD: "*redacted*" + + TZ: "Europe/London" + ENABLE_ROLLING_LOGS: "true" + + UID: "1000" + GID: "1000" + + # Modrinth auto-DL — TYPE=CUSTOM lets MODRINTH_LOADER=paper actually work + MODRINTH_PROJECTS: | + luckperms + placeholderapi + fastasyncworldedit + simple-voice-chat + minimotd + skinsrestorer + vaultunlocked + proantitab + voidworldgenerator + homestead-plugin + # EssentialsX + spark not on Modrinth as paper jars — direct GH/CI URLs. + # ExcellentShop/CoinsEngine/nightcore are mounted via COPY_PLUGINS_SRC, + # not URL'd here, so they survive even if NightExpress's reposilite goes + # dark. + PLUGINS: | + https://github.com/EssentialsX/Essentials/releases/download/2.21.2/EssentialsX-2.21.2.jar + https://ci.lucko.me/job/spark/lastSuccessfulBuild/artifact/spark-bukkit/build/libs/spark-1.10.172-bukkit.jar + https://github.com/HaHaWTH/AuthMeReReloaded/releases/download/b49/AuthMe-5.6.0-FORK-Universal.jar + https://github.com/dmulloy2/ProtocolLib/releases/download/dev-build/ProtocolLib.jar + https://github.com/VoidemLIVE/Help-Command-Plugin/releases/download/v2.9.2/HelpCommand-2.9.2.jar + https://cdn.modrinth.com/data/Lu3KuzdV/versions/HD2IvrxS/CoreProtect-CE-23.1.jar + https://cdn.modrinth.com/data/gG7VFbG0/versions/uWACk3HR/TAB%20v6.0.1.jar + MODRINTH_DOWNLOAD_DEPENDENCIES: none + MODRINTH_PROJECTS_DEFAULT_VERSION_TYPE: release + MODRINTH_LOADER: paper + SPIGET_RESOURCES: "" + REMOVE_OLD_MODS: "true" + REMOVE_OLD_MODS_INCLUDE: "*.jar" + # Custom-built jars survive the wipe via this exclude list. + # ExcellentShop, CoinsEngine, nightcore arrive via COPY_PLUGINS_SRC bind. + REMOVE_OLD_MODS_EXCLUDE: "AuthLimbo*.jar,ExcellentShop*.jar,CoinsEngine*.jar,nightcore*.jar" + COPY_PLUGINS_SRC: "/plugins-custom" + volumes: + - /opt/docker/minecraft:/data + - /opt/docker/minecraft-custom-plugins:/plugins-custom:ro + ports: + - "25565:25565/tcp" + - "25575:25575/tcp" + networks: + - proxy + restart: unless-stopped + # H2 (2026-05-07): Container hardening per AUDIT-2026-05-07.md F-06. + # Drop the default Docker capability set (CAP_NET_RAW, CAP_SYS_CHROOT, ...) + # which the JVM/Paper does not need. Re-add only the minimum needed by + # itzg's entrypoint chown/gosu flow. DAC_OVERRIDE intentionally omitted — + # add back only if entrypoint fails. NOT applied live until next restart. + cap_drop: + - ALL + cap_add: + - CHOWN + - SETUID + - SETGID + - FOWNER + security_opt: + - no-new-privileges:true + healthcheck: + test: ["CMD", "mc-health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 240s + deploy: + resources: + limits: + memory: 18G + cpus: '6' + pids: 4096 + reservations: + memory: 8G + labels: + - "traefik.enable=false" + +networks: + proxy: + external: true diff --git a/staging/excellentshop/scripts/lp-shop-migration.sh b/staging/excellentshop/scripts/lp-shop-migration.sh new file mode 100755 index 0000000..5c080a2 --- /dev/null +++ b/staging/excellentshop/scripts/lp-shop-migration.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# lp-shop-migration.sh — grant ExcellentShop + CoinsEngine perms to default group +# +# Adds the default-tier permissions players need to /shop, /sellall, /ah-list, +# and /ah-bid. Mirrors the perm surface that EZShop + Kiranhart AH gave for free. +# +# DOES NOT touch group prefixes/suffixes — those are operator-managed +# (feedback_lp_prefixes_locked.md). Only `permission set` calls. +# +# Run via: +# ssh user@192.168.0.100 'docker exec -i minecraft-mc \ +# bash -s' < lp-shop-migration.sh +# or copy this into the container and run there. + +set -euo pipefail + +LP="rcon-cli lp" + +run() { + echo "+ $*" >&2 + $LP "$@" >/dev/null +} + +# ---- ExcellentShop (Virtual Shop module) ---- +run group default permission set excellentshop.shop.use true +run group default permission set excellentshop.shop.buy.virtual true +run group default permission set excellentshop.shop.sell.virtual true +run group default permission set excellentshop.shop.sellall true +run group default permission set excellentshop.shop.sellhand true +run group default permission set excellentshop.shop.sellhandall true + +# ---- ExcellentShop (Auction House module) ---- +run group default permission set excellentshop.auction.use true +run group default permission set excellentshop.auction.list true +run group default permission set excellentshop.auction.bid true +run group default permission set excellentshop.auction.buy true +run group default permission set excellentshop.auction.cancel true +run group default permission set excellentshop.auction.expire.collect true +run group default permission set excellentshop.auction.sold.collect true + +# ---- CoinsEngine ---- +run group default permission set coinsengine.balance true +run group default permission set coinsengine.pay true +run group default permission set coinsengine.top true + +# ---- Operator/admin tier ---- +# The 'staff' group inherits these for moderation parity with the old +# Kiranhart 'auctionhouse.moderator' node. +run group staff permission set excellentshop.auction.admin true +run group staff permission set excellentshop.shop.admin true +run group staff permission set coinsengine.admin true + +# ---- Revoke legacy nodes (cleanup) ---- +# These map to EZShop / Kiranhart and become dead weight after the swap. +# Wrapped in `|| true` because they may not exist on a fresh perms tree. +$LP group default permission unset ezshop.use 2>/dev/null || true +$LP group default permission unset auctionhouse.use 2>/dev/null || true +$LP group default permission unset auctionhouse.list 2>/dev/null || true +$LP group default permission unset auctionhouse.bid 2>/dev/null || true +$LP group staff permission unset auctionhouse.moderator 2>/dev/null || true + +echo "lp-shop-migration: done." diff --git a/staging/excellentshop/scripts/lp-shop-rollback.sh b/staging/excellentshop/scripts/lp-shop-rollback.sh new file mode 100755 index 0000000..d862f53 --- /dev/null +++ b/staging/excellentshop/scripts/lp-shop-rollback.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# lp-shop-rollback.sh — reverse lp-shop-migration.sh +# Re-grants the legacy EZShop + Kiranhart perms and clears ExcellentShop nodes. + +set -euo pipefail + +LP="rcon-cli lp" + +run() { + echo "+ $*" >&2 + $LP "$@" >/dev/null +} + +# ---- Re-grant legacy ---- +run group default permission set ezshop.use true +run group default permission set auctionhouse.use true +run group default permission set auctionhouse.list true +run group default permission set auctionhouse.bid true +run group staff permission set auctionhouse.moderator true + +# ---- Drop ExcellentShop nodes ---- +for node in \ + excellentshop.shop.use \ + excellentshop.shop.buy.virtual \ + excellentshop.shop.sell.virtual \ + excellentshop.shop.sellall \ + excellentshop.shop.sellhand \ + excellentshop.shop.sellhandall \ + excellentshop.auction.use \ + excellentshop.auction.list \ + excellentshop.auction.bid \ + excellentshop.auction.buy \ + excellentshop.auction.cancel \ + excellentshop.auction.expire.collect \ + excellentshop.auction.sold.collect \ + coinsengine.balance \ + coinsengine.pay \ + coinsengine.top +do + $LP group default permission unset "$node" 2>/dev/null || true +done + +for node in excellentshop.auction.admin excellentshop.shop.admin coinsengine.admin; do + $LP group staff permission unset "$node" 2>/dev/null || true +done + +echo "lp-shop-rollback: done." diff --git a/staging/excellentshop/scripts/rollback.sh b/staging/excellentshop/scripts/rollback.sh new file mode 100755 index 0000000..0f5a51c --- /dev/null +++ b/staging/excellentshop/scripts/rollback.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# rollback.sh — reverse swap.sh: restore EZShop + Kiranhart, drop ExcellentShop +# OPERATOR-ONLY. Run if smoke-test fails after swap.sh. +# Restores the most-recent plugins.tar.bak-* snapshot left by swap.sh. + +set -euo pipefail + +COMPOSE_DIR=/opt/docker/minecraft +CUSTOM_PLUGIN_DIR=/opt/docker/minecraft-custom-plugins +STAGING_DIR=$(cd "$(dirname "$0")/.." && pwd) + +log() { printf '\033[1;33m[rollback %s]\033[0m %s\n' "$(date +%H:%M:%S)" "$*"; } +die() { printf '\033[1;31m[rollback FAIL]\033[0m %s\n' "$*" >&2; exit 1; } + +LATEST_BAK=$(sudo ls -1t /opt/docker/minecraft/plugins.tar.bak-* 2>/dev/null | head -1) +[[ -n "$LATEST_BAK" ]] || die "no plugins.tar.bak-* snapshot found in /opt/docker/minecraft/" +log "using snapshot: $LATEST_BAK" + +log "1/6: stopping minecraft-mc ..." +sudo docker compose -f "$COMPOSE_DIR/docker-compose.yml" stop mc + +log "2/6: removing ExcellentShop + CoinsEngine + nightcore ..." +sudo docker run --rm --privileged --userns=host \ + -v /opt/docker/minecraft:/data \ + docker.io/library/alpine:3 \ + sh -c "rm -rf /data/plugins/ExcellentShop /data/plugins/ExcellentShop-*.jar \ + /data/plugins/CoinsEngine /data/plugins/CoinsEngine-*.jar \ + /data/plugins/nightcore /data/plugins/nightcore-*.jar" + +log "3/6: clearing custom-plugins bind dir ..." +sudo rm -f "$CUSTOM_PLUGIN_DIR/ExcellentShop-"*.jar \ + "$CUSTOM_PLUGIN_DIR/CoinsEngine-"*.jar \ + "$CUSTOM_PLUGIN_DIR/nightcore-"*.jar + +log "4/6: restoring legacy EZShop + AuctionHouse from $LATEST_BAK ..." +sudo docker run --rm --privileged --userns=host \ + -v /opt/docker/minecraft:/data \ + docker.io/library/alpine:3 \ + sh -c "tar -xzf $(basename "$LATEST_BAK") -C /data && cd /data && ls plugins/EZShop plugins/AuctionHouse" + +log "5/6: reverting compose (restore most-recent .bak) ..." +LATEST_COMPOSE_BAK=$(ls -1t "$COMPOSE_DIR/docker-compose.yml.bak-"* 2>/dev/null | head -1) +if [[ -n "$LATEST_COMPOSE_BAK" ]]; then + cp "$LATEST_COMPOSE_BAK" "$COMPOSE_DIR/docker-compose.yml" + log " restored compose: $LATEST_COMPOSE_BAK" +fi + +log "6/6: starting minecraft-mc + reverse-LP migration ..." +sudo docker compose -f "$COMPOSE_DIR/docker-compose.yml" up -d mc + +deadline=$(( $(date +%s) + 240 )) +until sudo docker exec minecraft-mc mc-health 2>/dev/null; do + [[ $(date +%s) -gt $deadline ]] && die "server did not come healthy in 4 minutes" + sleep 5 +done + +sudo docker exec -i minecraft-mc bash -s < "$STAGING_DIR/scripts/lp-shop-rollback.sh" + +log "DONE. Verify /shop and /ah are back to legacy behaviour." diff --git a/staging/excellentshop/scripts/swap.sh b/staging/excellentshop/scripts/swap.sh new file mode 100755 index 0000000..a5ff50c --- /dev/null +++ b/staging/excellentshop/scripts/swap.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# swap.sh — racked.ru EZShop+Kiranhart -> ExcellentShop+CoinsEngine swap +# +# OPERATOR-ONLY. Run on nullstone (192.168.0.100) as `user`. +# This script DOES restart the minecraft container. Schedule a 30-minute +# maintenance window before invoking. +# +# Pre-flight (manual, before this script): +# 1. Announce 1-week then 1-day then 1-hour heads-up via /pm news. +# 2. Drain or expire all active Kiranhart AH listings (refund stuck items). +# 3. Snapshot /opt/docker/minecraft via restic (see scripts/restic-backup-playerdata.sh). +# 4. Confirm the staged jars are present and checksums match the bundle on disk. +# +# Post-flight (manual, after this script): +# 1. /shop -- verify the main category menu opens, 11 shops visible. +# 2. /ah -- verify auction house opens, listing flow works (test admin item). +# 3. /sellall -- run with a test stack, verify CoinsEngine balance updated. +# 4. /balance -- verify EssentialsX/Vault still resolves through CoinsEngine. +# 5. Smoke-test a non-staff player join: shop perms, listing perms. +# 6. Tail logs for `nightcore` and `excellentshop` errors for ~5 minutes. + +set -euo pipefail + +COMPOSE_DIR=/opt/docker/minecraft +PLUGIN_DIR=/opt/docker/minecraft/plugins +CUSTOM_PLUGIN_DIR=/opt/docker/minecraft-custom-plugins +STAGING_DIR=$(cd "$(dirname "$0")/.." && pwd) +TS=$(date +%Y%m%d-%H%M%S) + +log() { printf '\033[1;36m[swap %s]\033[0m %s\n' "$(date +%H:%M:%S)" "$*"; } +die() { printf '\033[1;31m[swap FAIL]\033[0m %s\n' "$*" >&2; exit 1; } + +[[ -d "$STAGING_DIR/build" ]] || die "staging build dir missing: $STAGING_DIR/build" +for j in ExcellentShop-5.0.1.jar CoinsEngine-2.7.0.jar nightcore-2.15.3.jar; do + [[ -f "$STAGING_DIR/build/$j" ]] || die "missing jar: $j" +done + +log "1/8: stopping minecraft-mc..." +sudo docker compose -f "$COMPOSE_DIR/docker-compose.yml" stop mc + +log "2/8: backing up old plugin tree to /opt/docker/minecraft.bak-$TS ..." +sudo docker run --rm --privileged --userns=host \ + -v /opt/docker/minecraft:/data \ + docker.io/library/alpine:3 \ + sh -c "tar -czf /data/plugins.tar.bak-$TS -C /data plugins/EZShop plugins/EZShop-1.0-SNAPSHOT.jar plugins/AuctionHouse plugins/AuctionHouse-1.4.6.jar 2>/dev/null || true" + +log "3/8: removing legacy EZShop + AuctionHouse ..." +sudo docker run --rm --privileged --userns=host \ + -v /opt/docker/minecraft:/data \ + docker.io/library/alpine:3 \ + sh -c "rm -rf /data/plugins/EZShop /data/plugins/EZShop-*.jar /data/plugins/AuctionHouse /data/plugins/AuctionHouse-*.jar" + +log "4/8: ensuring custom-plugins bind dir exists ..." +sudo mkdir -p "$CUSTOM_PLUGIN_DIR" +sudo cp "$STAGING_DIR/build/"*.jar "$CUSTOM_PLUGIN_DIR/" +sudo chown -R 1000:1000 "$CUSTOM_PLUGIN_DIR" +ls -la "$CUSTOM_PLUGIN_DIR" + +log "5/8: applying compose patch (custom plugins bind + EXCLUDE list) ..." +if ! grep -q "minecraft-custom-plugins" "$COMPOSE_DIR/docker-compose.yml"; then + cp "$COMPOSE_DIR/docker-compose.yml" "$COMPOSE_DIR/docker-compose.yml.bak-$TS" + sudo cp "$STAGING_DIR/scripts/docker-compose.patch.yml" "$COMPOSE_DIR/docker-compose.yml" +fi + +log "6/8: starting minecraft-mc (first start will create configs) ..." +sudo docker compose -f "$COMPOSE_DIR/docker-compose.yml" up -d mc + +log "7/8: waiting for server to be healthy (max 4 min) ..." +deadline=$(( $(date +%s) + 240 )) +until sudo docker exec minecraft-mc mc-health 2>/dev/null; do + [[ $(date +%s) -gt $deadline ]] && die "server did not come healthy in 4 minutes" + sleep 5 +done + +log "8/8: applying staged config overrides + LP perms ..." +# copy staged configs into the now-populated plugin tree +sudo docker cp "$STAGING_DIR/configs/excellentshop/config.yml" minecraft-mc:/data/plugins/ExcellentShop/config.yml +sudo docker cp "$STAGING_DIR/configs/excellentshop/modules/virtual_shop/config.yml" minecraft-mc:/data/plugins/ExcellentShop/modules/virtual_shop/config.yml +sudo docker cp "$STAGING_DIR/configs/excellentshop/modules/auction/config.yml" minecraft-mc:/data/plugins/ExcellentShop/modules/auction/config.yml +sudo docker cp "$STAGING_DIR/configs/coinsengine/config.yml" minecraft-mc:/data/plugins/CoinsEngine/config.yml +sudo docker cp "$STAGING_DIR/configs/coinsengine/currencies/coin.yml" minecraft-mc:/data/plugins/CoinsEngine/currencies/coin.yml +sudo docker exec minecraft-mc rcon-cli excellentshop reload || true +sudo docker exec minecraft-mc rcon-cli coinsengine reload || true + +# LP perms migration +sudo docker exec -i minecraft-mc bash -s < "$STAGING_DIR/scripts/lp-shop-migration.sh" + +log "DONE. Verify in-game now:" +log " /shop <- main category menu" +log " /ah <- auction house" +log " /sellall <- bulk sell" +log " /balance <- CoinsEngine via Vault" +log "" +log "If anything is broken, run: $STAGING_DIR/scripts/rollback.sh"