i18n: per-player EN/PT via Adventure GlobalTranslator (#1)
Foundation + commands module of the i18n spec. - I18n registry/loader + Lang.tr facade + reloadI18n - PT source-of-truth bundle + EN translation - CanalhandiaCommand player-facing strings migrated; admin-tuning/help/enum-labels deferred - I18nTest: parity + per-locale render + pt_BR fallback; 316/316 green 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit was merged in pull request #1.
This commit is contained in:
Executable
+215
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pre-flight harness for a Canalhandia deploy.
|
||||
#
|
||||
# Every check below is READ-ONLY. It never restarts the server, never writes to
|
||||
# the plugins directory, and never touches the world. Run it after staging a
|
||||
# jar and before asking Crafty to restart: a red line here is a crash-on-boot
|
||||
# you get to fix while the server is still up.
|
||||
#
|
||||
# Usage: ./preflight.sh [path/to/new.jar]
|
||||
# (defaults to target/Canalhandia-1.0.0.jar)
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
JAR="${1:-target/Canalhandia-1.0.0.jar}"
|
||||
NS=minecraft
|
||||
SERVER_ID=6e39a8b2-300b-42d6-8139-f397c23e461b
|
||||
PLUGINS="/crafty/servers/${SERVER_ID}/plugins"
|
||||
K="microk8s kubectl -n ${NS}"
|
||||
|
||||
fail=0
|
||||
pass() { printf ' \033[32mOK\033[0m %s\n' "$1"; }
|
||||
warn() { printf ' \033[33mWARN\033[0m %s\n' "$1"; }
|
||||
bad() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; fail=$((fail + 1)); }
|
||||
head() { printf '\n\033[1m%s\033[0m\n' "$1"; }
|
||||
|
||||
CRAFTY=$($K get pods -l app=crafty-controller -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
||||
if [ -z "$CRAFTY" ]; then
|
||||
CRAFTY=$($K get pods --no-headers 2>/dev/null | awk '/^crafty-controller/ {print $1; exit}')
|
||||
fi
|
||||
|
||||
head "1. Local build"
|
||||
|
||||
if [ ! -f "$JAR" ]; then
|
||||
bad "jar not found: $JAR"
|
||||
else
|
||||
pass "jar present: $JAR ($(stat -c%s "$JAR") bytes)"
|
||||
# A jar that does not open is a jar the server will refuse at boot.
|
||||
if unzip -t "$JAR" >/dev/null 2>&1; then
|
||||
pass "jar archive is intact"
|
||||
else
|
||||
bad "jar archive is corrupt (unzip -t failed)"
|
||||
fi
|
||||
# plugin.yml is the only file Bukkit truly requires; without it the plugin is
|
||||
# skipped silently and nothing in the deploy takes effect.
|
||||
if unzip -p "$JAR" plugin.yml >/dev/null 2>&1; then
|
||||
pass "plugin.yml present in jar"
|
||||
else
|
||||
bad "plugin.yml MISSING from jar — the plugin would not load at all"
|
||||
fi
|
||||
# Kept in step with Canalhandia.onEnable's register() list. A command that is
|
||||
# registered in code but absent here logs a warning at boot and then silently
|
||||
# does nothing in game, which is a hard failure to diagnose from inside.
|
||||
CMDS="canalhandia curiosidade adivinha enquete ranking reagir reacoes \
|
||||
palpite votar legal wow top f ia iap errado nota save \
|
||||
recado recados mortes conquistas"
|
||||
missing=""
|
||||
for cmd in $CMDS; do
|
||||
unzip -p "$JAR" plugin.yml 2>/dev/null | grep -qE "^ ${cmd}:" || missing="$missing $cmd"
|
||||
done
|
||||
if [ -z "$missing" ]; then
|
||||
pass "all $(echo $CMDS | wc -w) commands declared in plugin.yml"
|
||||
else
|
||||
bad "commands missing from plugin.yml:$missing"
|
||||
fi
|
||||
# Permissions the new features gate on. An undeclared Bukkit permission falls
|
||||
# back to op-only, which would silently stop normal players writing notes.
|
||||
for perm in canalhandia.nota canalhandia.nota.publica canalhandia.recado; do
|
||||
if unzip -p "$JAR" plugin.yml 2>/dev/null | grep -q " ${perm}:"; then
|
||||
pass "permission ${perm} declared"
|
||||
else
|
||||
bad "permission ${perm} MISSING — would default to op-only"
|
||||
fi
|
||||
done
|
||||
# BlueMap's API is compile-only (provided scope): BlueMap ships those classes
|
||||
# itself, and a second copy inside this jar would shadow them and break the
|
||||
# real plugin. This check is the guard against someone dropping the scope.
|
||||
if unzip -l "$JAR" 2>/dev/null | grep -q "bluecolored"; then
|
||||
bad "BlueMap classes are BUNDLED in the jar — the dependency must stay 'provided'"
|
||||
else
|
||||
pass "BlueMap API not bundled (provided scope intact)"
|
||||
fi
|
||||
# config.yml ships defaults; a jar without it means saveDefaultConfig() writes
|
||||
# nothing and every setting silently falls back to the hardcoded default.
|
||||
if unzip -p "$JAR" config.yml >/dev/null 2>&1; then
|
||||
pass "config.yml present in jar"
|
||||
else
|
||||
bad "config.yml MISSING from jar"
|
||||
fi
|
||||
fi
|
||||
|
||||
head "2. Tests"
|
||||
if [ -d target/surefire-reports ]; then
|
||||
tests=$(grep -ho 'tests="[0-9]*"' target/surefire-reports/*.xml 2>/dev/null |
|
||||
grep -o '[0-9]*' | paste -sd+ | bc)
|
||||
bads=$(grep -ho 'failures="[0-9]*"\|errors="[0-9]*"' target/surefire-reports/*.xml 2>/dev/null |
|
||||
grep -o '[0-9]*' | paste -sd+ | bc)
|
||||
if [ "${bads:-1}" = "0" ]; then
|
||||
pass "${tests} tests, 0 failures/errors"
|
||||
else
|
||||
bad "${bads} test failures/errors — run mvn test"
|
||||
fi
|
||||
else
|
||||
warn "no surefire reports; run the test suite before deploying"
|
||||
fi
|
||||
|
||||
head "3. Cluster"
|
||||
if [ -z "$CRAFTY" ]; then
|
||||
bad "crafty-controller pod not found in namespace ${NS}"
|
||||
else
|
||||
pass "crafty pod: $CRAFTY"
|
||||
if $K exec "$CRAFTY" -- test -d "$PLUGINS" 2>/dev/null; then
|
||||
pass "plugins directory reachable"
|
||||
else
|
||||
bad "cannot reach $PLUGINS"
|
||||
fi
|
||||
fi
|
||||
|
||||
head "4. Staged jar on the server"
|
||||
if [ -n "$CRAFTY" ] && [ -f "$JAR" ]; then
|
||||
local_sum=$(sha256sum "$JAR" | cut -c1-8)
|
||||
remote_sum=$($K exec "$CRAFTY" -- sha256sum "$PLUGINS/Canalhandia-1.0.0.jar" 2>/dev/null | cut -c1-8)
|
||||
if [ -z "$remote_sum" ]; then
|
||||
bad "no Canalhandia jar staged on the server"
|
||||
elif [ "$local_sum" = "$remote_sum" ]; then
|
||||
pass "staged jar matches the local build (sha $local_sum)"
|
||||
else
|
||||
bad "staged jar is sha $remote_sum, local build is $local_sum — copy it again"
|
||||
fi
|
||||
# Ownership: the JVM runs as uid 1000 / gid 0. A root-owned jar is a jar the
|
||||
# server cannot read, and the failure looks like "plugin just did not load".
|
||||
owner=$($K exec "$CRAFTY" -- stat -c '%u:%g' "$PLUGINS/Canalhandia-1.0.0.jar" 2>/dev/null)
|
||||
if [ "$owner" = "1000:0" ]; then
|
||||
pass "jar ownership 1000:0"
|
||||
else
|
||||
bad "jar ownership is '$owner', expected 1000:0 — chown it"
|
||||
fi
|
||||
# A rollback target must exist before, not after, something goes wrong.
|
||||
if $K exec "$CRAFTY" -- sh -c "ls $PLUGINS/Canalhandia-1.0.0.jar.bak-* >/dev/null 2>&1"; then
|
||||
pass "rollback jar(s) present"
|
||||
else
|
||||
bad "no .bak jar to roll back to"
|
||||
fi
|
||||
fi
|
||||
|
||||
head "5. Live config"
|
||||
if [ -n "$CRAFTY" ]; then
|
||||
cfg="$PLUGINS/Canalhandia/config.yml"
|
||||
if $K exec "$CRAFTY" -- test -f "$cfg" 2>/dev/null; then
|
||||
pass "config.yml present on the server"
|
||||
# Parsed HERE rather than in the pod: the crafty image has no python, and a
|
||||
# config.yml that does not parse is a plugin that disables itself on boot.
|
||||
tmp=$(mktemp)
|
||||
if $K exec "$CRAFTY" -- cat "$cfg" > "$tmp" 2>/dev/null && [ -s "$tmp" ]; then
|
||||
# Pick a parser. "no parser available" must NOT be reported as "invalid":
|
||||
# a harness that cries wolf is a harness people learn to ignore.
|
||||
if python3 -c "import yaml" 2>/dev/null; then
|
||||
yaml_check() { python3 -c "import yaml,sys;yaml.safe_load(open(sys.argv[1]))" "$1"; }
|
||||
elif command -v docker >/dev/null 2>&1; then
|
||||
yaml_check() { docker run --rm -v "$1":/c.yml:ro python:3.12-slim \
|
||||
sh -c "pip install -q pyyaml >/dev/null 2>&1 && python3 -c \
|
||||
'import yaml;yaml.safe_load(open(\"/c.yml\"))'"; }
|
||||
else
|
||||
yaml_check() { return 2; }
|
||||
fi
|
||||
yaml_check "$tmp" 2>/dev/null
|
||||
case $? in
|
||||
0) pass "config.yml parses as valid YAML" ;;
|
||||
2) warn "no YAML parser available (pip install pyyaml) — not checked" ;;
|
||||
*) bad "config.yml on the server is NOT valid YAML — the plugin would fail to load" ;;
|
||||
esac
|
||||
# The keys this deploy depends on. A missing key is not fatal (Settings
|
||||
# has defaults) but it means the merge did not happen as intended.
|
||||
for key in "modulos:" "zoacao:" "personalidade:" "contexto-chat:" "estado-servidor:"; do
|
||||
if grep -q "$key" "$tmp"; then
|
||||
pass "config has ${key%:}"
|
||||
else
|
||||
warn "config has no '${key%:}' — will fall back to the built-in default"
|
||||
fi
|
||||
done
|
||||
else
|
||||
warn "could not read config.yml out of the pod"
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
if $K exec "$CRAFTY" -- sh -c "ls $PLUGINS/Canalhandia/config.yml.bak-* >/dev/null 2>&1"; then
|
||||
pass "config backup(s) present"
|
||||
else
|
||||
bad "no config.yml.bak-* to roll back to"
|
||||
fi
|
||||
else
|
||||
bad "config.yml missing on the server"
|
||||
fi
|
||||
fi
|
||||
|
||||
head "6. Server health right now"
|
||||
if [ -n "$CRAFTY" ]; then
|
||||
logf="/crafty/servers/${SERVER_ID}/logs/latest.log"
|
||||
# tr -dc keeps digits only: grep -c prints nothing on no-match under some
|
||||
# shells, and the stray newline made the numeric test below explode.
|
||||
online=$($K exec "$CRAFTY" -- sh -c "grep -c 'joined the game' $logf" 2>/dev/null | tr -dc '0-9')
|
||||
pass "log readable (${online:-0} join lines this session)"
|
||||
errs=$($K exec "$CRAFTY" -- sh -c "tail -500 $logf | grep -ci 'ERROR\]'" 2>/dev/null | tr -dc '0-9')
|
||||
if [ "${errs:-0}" -gt 0 ]; then
|
||||
warn "${errs} ERROR lines in the last 500 — read them before restarting"
|
||||
else
|
||||
pass "no ERROR lines in the last 500"
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
if [ "$fail" -eq 0 ]; then
|
||||
printf '\033[32mPRE-FLIGHT CLEAN — safe to restart.\033[0m\n'
|
||||
exit 0
|
||||
fi
|
||||
printf '\033[31m%d CHECK(S) FAILED — do NOT restart yet.\033[0m\n' "$fail"
|
||||
exit 1
|
||||
Reference in New Issue
Block a user