feat(shop): stage ExcellentShop+CoinsEngine migration bundle

Replaces EZShop 1.0-SNAPSHOT (bespoke, sell-only, 27-slot) and Kiranhart
AuctionHouse 1.4.6 (ARR no-LICENSE, dupe history) with a single GPL-3
stack: ExcellentShop 5.0.1 + CoinsEngine 2.7.0 + nightcore 2.15.3.

Per SHOP-SYSTEM-DECISION.md (commit 9565f0b), Stack A wins on three
counts: GPL-3 source (vs ARR/proprietary), unified theme across shop
and AH, single-vendor support story under NightExpress.

Jars sourced from upstream Reposilite repo.nightexpressdev.com — same
artefacts a local mvn package would produce, just reproducible without
the alex9849 integration that breaks on TLS handshake. SHA256SUMS
committed for receipt; never re-fetched at swap time.

Bundle:
  build/    — three jars + SHA256SUMS (~2.1MB total)
  configs/  — post-first-run overrides (chest module off, /shop alias,
              AH 1% tax + BIN+bid + 10 listings matches Kiranhart)
  scripts/  — swap.sh, rollback.sh, lp-shop-migration.sh,
              lp-shop-rollback.sh, docker-compose.patch.yml

itzg integration: COPY_PLUGINS_SRC=/plugins-custom mount per
ITZG-CUSTOM-JAR-PERSISTENCE.md, plus REMOVE_OLD_MODS_EXCLUDE expanded
with the three new globs.

LP migration grants default-tier excellentshop.* + coinsengine.*
nodes; staff tier gets *.admin equivalents to legacy
auctionhouse.moderator. Group prefixes/suffixes untouched per
feedback_lp_prefixes_locked.md.

DOES NOT touch live /data/plugins/EZShop or /data/plugins/AuctionHouse
— staging only. Operator runs swap.sh in scheduled maintenance window.

Refs: SHOP-SYSTEM-DECISION.md, AUDIT-2026-05-07.md F-11,
ITZG-CUSTOM-JAR-PERSISTENCE.md, MIGRATION-PLAN-EXCELLENTSHOP.md.
This commit is contained in:
s8n 2026-05-08 00:01:53 +01:00
parent 9565f0bf53
commit 4116d67eaf
18 changed files with 907 additions and 0 deletions

View file

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

View file

@ -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-<ts>` |
| 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-<ts>` |
| 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 <listing>` 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-<ts>` 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 <name> 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 <node> 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 <player> <prev-balance>` 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.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,3 @@
cbb18ee42388acd2b1eac58eacba44db6de6b2f8939981e7e6eb106314db31a2 CoinsEngine-2.7.0.jar
a91cb85af94b77097eaf14cae0058b7b0a0001df2cc4c9ab8d7f8710bdb1ad1e ExcellentShop-5.0.1.jar
b1313e0455c41bc2bad5de962fa80a59ba9ca8a42c6385d7fa3291d7fb0bb803 nightcore-2.15.3.jar

Binary file not shown.

View file

@ -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/<id>/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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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