Files
ulicraft-server-v1/tooling/export-server-config.sh
T
oierandClaude Opus 5 92c6f5fc8f feat(compose): mount mods, config and kubejs writable
Drop :ro from ./server-mods:/mods and the four kubejs source subdirs, and
add a new ./server-config:/data/config bind — mod config used to live only
inside the mc_data volume, unreachable from the host without root.

The config bind needs seeding: a bind mount shadows what is under it, so an
empty ./server-config hides the generated config and every mod falls back to
defaults. tooling/export-server-config.sh copies /data/config out of the
volume first, halting if the target is non-empty, if the container is still
running, or if the mc_data volume name is ambiguous.

Writable mounts also need the server's uid (itzg runs as 1000) to have write
access on the host dir; oxps' host user is 1002, hence MC_UID/MC_GID.

The kubejs ROOT still must not be mounted — BaseProperties.save() NPEs and
kubejs never initialises. That is about mounting the root at all, not about
the mode, so dropping :ro does not change it. It does mean kubejs writes now
land in $DISTRIBUTION_WEB_ROOT as uncommitted changes in that host's checkout.

Also documents how to reach the mc_data volume (exec/cp, resolved host path,
stop-before-write rule) in plan/05-minecraft.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 00:46:30 +02:00

88 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# export-server-config.sh — seed ./server-config/ from /data/config inside the
# mc_data volume, so the compose bind mount `./server-config:/data/config` can
# take over without losing the mod configs the server already generated.
#
# WHY: a bind mount shadows whatever is under it. Mounting an empty host dir over
# /data/config hides every existing config file and each mod regenerates its
# defaults — silently reverting tuning. Run this ONCE, before the first boot with
# the mount in place.
#
# Cautious by design: refuses to run if the target already has content, if the
# volume can't be resolved unambiguously, or if the server is still running.
#
# Usage:
# tooling/export-server-config.sh # export, then chown to MC_UID:MC_GID
# MC_UID=1002 MC_GID=1002 tooling/… # oxps (host user oier = 1002)
set -euo pipefail
cd "$(dirname "$0")/.." # repo root
[ -f .env ] && { set -a; . ./.env; set +a; }
TARGET="./server-config"
MC_UID="${MC_UID:-1000}"
MC_GID="${MC_GID:-1000}"
# ── resolve the mc_data volume ──────────────────────────────────────────────
# Compose prefixes the volume with the project name (defaults to the repo dir
# name), so never hardcode it — match and require exactly one.
mapfile -t VOLS < <(docker volume ls -q | grep -E '(^|_)mc_data$' || true)
if [ "${#VOLS[@]}" -eq 0 ]; then
echo "FATAL: no mc_data volume found (docker volume ls). Nothing to export." >&2
echo " If this is a fresh host there is no config yet — create an empty" >&2
echo " $TARGET and let the server generate defaults into it." >&2
exit 1
fi
if [ "${#VOLS[@]}" -gt 1 ]; then
echo "FATAL: multiple mc_data volumes, refusing to guess:" >&2
printf ' %s\n' "${VOLS[@]}" >&2
exit 1
fi
VOL="${VOLS[0]}"
# ── refuse while the server is live (it rewrites config on shutdown) ────────
if [ -n "$(docker ps -q -f name='^minecraft$')" ]; then
echo "FATAL: the minecraft container is running. Stop it first:" >&2
echo " docker compose stop minecraft" >&2
exit 1
fi
# ── refuse to clobber an existing export ────────────────────────────────────
if [ -d "$TARGET" ] && [ -n "$(ls -A "$TARGET" 2>/dev/null)" ]; then
echo "FATAL: $TARGET already has content — refusing to overwrite." >&2
echo " Move it aside if you really want a fresh export." >&2
exit 1
fi
mkdir -p "$TARGET"
# ── copy out of the volume ──────────────────────────────────────────────────
echo "Exporting $VOL:/data/config -> $TARGET"
docker run --rm \
-v "$VOL":/data:ro \
-v "$PWD/$TARGET":/out \
alpine sh -euc '
if [ ! -d /data/config ]; then
echo "FATAL: /data/config does not exist in the volume." >&2
exit 1
fi
cp -a /data/config/. /out/
'
# ── ownership: the server (uid 1000 under itzg) must be able to write ───────
docker run --rm -v "$PWD/$TARGET":/out \
alpine chown -R "$MC_UID:$MC_GID" /out
COUNT=$(find "$TARGET" -type f | wc -l)
echo "Exported $COUNT files, owned by $MC_UID:$MC_GID."
if [ "$(id -u)" != "$MC_UID" ]; then
echo
echo "NOTE: your host uid ($(id -u)) != $MC_UID, so $TARGET is not yours to edit." >&2
echo " Either edit via sudo, or add group write and join the group." >&2
fi
echo
echo "Next: the compose bind mount ./server-config:/data/config now takes over."
echo " docker compose up -d --force-recreate minecraft"