minecraft / Dockerfile
Nodiw52992's picture
Update Dockerfile
e043635 verified
Raw
History Blame Contribute Delete
9.64 kB
FROM eclipse-temurin:17-jre-jammy
USER root
RUN apt-get update && \
apt-get install -y --no-install-recommends \
bash \
curl \
jq \
ca-certificates \
coreutils \
findutils \
python3 && \
rm -rf /var/lib/apt/lists/*
# ── Bake Vineflower into the image at build time ──────────────────────────────
RUN curl -fL \
-o /app/vineflower.jar \
https://github.com/Vineflower/vineflower/releases/download/1.11.2/vineflower-1.11.2.jar && \
java -jar /app/vineflower.jar --help > /dev/null 2>&1 || true
WORKDIR /app
EXPOSE 7860
CMD ["bash", "-lc", "\
set -u; \
BASE=/data/minecraft; \
TMP=/tmp/mc-sync; \
MANIFEST_URL=https://piston-meta.mojang.com/mc/game/version_manifest_v2.json; \
MANIFEST_PATH=$BASE/version_manifest_v2.json; \
LOCKDIR=$BASE/.lock; \
LOG=$BASE/download.log; \
PROGRESS=$BASE/progress.txt; \
JOBS=${JOBS:-8}; \
DECOMPILE_JOBS=${DECOMPILE_JOBS:-4}; \
VINEFLOWER=/app/vineflower.jar; \
mkdir -p $BASE/server $BASE/client $TMP; \
printf 'starting\\n' > $PROGRESS; \
\
cat > /app/web.py <<'PY'\n\
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer\n\
import os, html\n\
BASE='/data/minecraft'\n\
class H(BaseHTTPRequestHandler):\n\
def do_GET(self):\n\
self.send_response(200)\n\
self.send_header('Content-Type','text/html; charset=utf-8')\n\
self.end_headers()\n\
prog='starting'\n\
log=''\n\
try:\n\
with open(os.path.join(BASE,'progress.txt'),'r',encoding='utf-8',errors='ignore') as f: prog=f.read()\n\
except: pass\n\
try:\n\
with open(os.path.join(BASE,'download.log'),'r',encoding='utf-8',errors='ignore') as f: log=''.join(f.readlines()[-200:])\n\
except: pass\n\
self.wfile.write(f'''<!doctype html><html><head><meta charset=\"utf-8\"><meta http-equiv=\"refresh\" content=\"2\"><title>Minecraft Sync</title><style>body{{font-family:Arial;background:#111;color:#eee;padding:20px}}pre{{white-space:pre-wrap;background:#000;padding:12px;border-radius:8px}}.c{{max-width:1100px;margin:auto}}</style></head><body><div class=\"c\"><h2>Minecraft Official Artifact Sync + Decompile</h2><h3>Progress</h3><pre>{html.escape(prog)}</pre><h3>Recent Logs</h3><pre>{html.escape(log)}</pre></div></body></html>'''.encode())\n\
def log_message(self,*a):\n\
return\n\
ThreadingHTTPServer(('0.0.0.0',7860),H).serve_forever()\n\
PY\n\
python3 /app/web.py & \
\
log(){ printf '[%s] %s\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$*\" | tee -a $LOG; printf '%s\\n' \"$*\" > $PROGRESS; }; \
cleanup_tmp(){ rm -rf $TMP/*; }; \
acquire_lock(){ if mkdir $LOCKDIR 2>/dev/null; then trap 'rm -rf \"$LOCKDIR\"; rm -rf \"$TMP\"/*' EXIT INT TERM; return 0; fi; log 'Another sync appears to be running'; exit 1; }; \
download_atomic(){ \
url=\"$1\"; dest=\"$2\"; expected_sha1=\"${3:-}\"; \
mkdir -p \"$(dirname \"$dest\")\"; \
part=\"${dest}.part\"; \
rm -f \"$part\"; \
if ! curl -fL --retry 8 --retry-delay 2 --connect-timeout 20 --max-time 3600 -A 'Mozilla/5.0' -o \"$part\" \"$url\"; then rm -f \"$part\"; return 1; fi; \
if [ ! -s \"$part\" ]; then rm -f \"$part\"; return 1; fi; \
if [ -n \"$expected_sha1\" ]; then \
actual_sha1=\"$(sha1sum \"$part\" | awk '{print $1}')\"; \
if [ \"$actual_sha1\" != \"$expected_sha1\" ]; then \
log \"SHA1 mismatch for $dest expected=$expected_sha1 actual=$actual_sha1\"; \
rm -f \"$part\"; return 1; \
fi; \
fi; \
mv -f \"$part\" \"$dest\"; return 0; \
}; \
is_valid_existing(){ \
file=\"$1\"; expected_sha1=\"${2:-}\"; \
[ -s \"$file\" ] || return 1; \
if [ -n \"$expected_sha1\" ]; then \
actual_sha1=\"$(sha1sum \"$file\" | awk '{print $1}')\"; \
[ \"$actual_sha1\" = \"$expected_sha1\" ] || return 1; \
fi; \
return 0; \
}; \
\
# ── Download worker ────────────────────────────────────────────────────────────
cat > /app/worker.sh <<'SH'\n\
#!/bin/bash\n\
set -u\n\
BASE=\"$1\"\n\
TMP=\"$2\"\n\
LOG=\"$3\"\n\
PROGRESS=\"$4\"\n\
ITEM=\"$5\"\n\
log(){ printf '[%s] %s\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$*\" | tee -a \"$LOG\"; printf '%s\\n' \"$*\" > \"$PROGRESS\"; }\n\
download_atomic(){\n\
url=\"$1\"; dest=\"$2\"; expected_sha1=\"${3:-}\"\n\
mkdir -p \"$(dirname \"$dest\")\"\n\
part=\"${dest}.part\"\n\
rm -f \"$part\"\n\
if ! curl -fL --retry 8 --retry-delay 2 --connect-timeout 20 --max-time 3600 -A 'Mozilla/5.0' -o \"$part\" \"$url\"; then rm -f \"$part\"; return 1; fi\n\
if [ ! -s \"$part\" ]; then rm -f \"$part\"; return 1; fi\n\
if [ -n \"$expected_sha1\" ]; then\n\
actual_sha1=\"$(sha1sum \"$part\" | awk '{print $1}')\"\n\
[ \"$actual_sha1\" = \"$expected_sha1\" ] || { log \"SHA1 mismatch for $dest\"; rm -f \"$part\"; return 1; }\n\
fi\n\
mv -f \"$part\" \"$dest\"\n\
}\n\
is_valid_existing(){\n\
file=\"$1\"; expected_sha1=\"${2:-}\"\n\
[ -s \"$file\" ] || return 1\n\
if [ -n \"$expected_sha1\" ]; then\n\
actual_sha1=\"$(sha1sum \"$file\" | awk '{print $1}')\"\n\
[ \"$actual_sha1\" = \"$expected_sha1\" ] || return 1\n\
fi\n\
return 0\n\
}\n\
id=\"$(printf '%s' \"$ITEM\" | cut -d'|' -f1)\"\n\
meta_url=\"$(printf '%s' \"$ITEM\" | cut -d'|' -f2-)\"\n\
tmp_json=\"$TMP/${id}.json\"\n\
log \"$id fetching version metadata\"\n\
download_atomic \"$meta_url\" \"$tmp_json\" '' || { log \"$id failed metadata\"; exit 0; }\n\
for side in client server; do\n\
dl_url=\"$(jq -r \".downloads.${side}.url // empty\" \"$tmp_json\")\"\n\
dl_sha1=\"$(jq -r \".downloads.${side}.sha1 // empty\" \"$tmp_json\")\"\n\
[ -n \"$dl_url\" ] || { log \"$id [$side] no official download entry, skipping\"; continue; }\n\
dir=\"$BASE/$side/$id\"\n\
json_dest=\"$dir/$id.json\"\n\
jar_dest=\"$dir/${side}.jar\"\n\
mkdir -p \"$dir\"\n\
cp -f \"$tmp_json\" \"$json_dest\"\n\
if is_valid_existing \"$jar_dest\" \"$dl_sha1\"; then log \"$id [$side] already complete, skipping\"; continue; fi\n\
[ ! -e \"$jar_dest\" ] || rm -f \"$jar_dest\"\n\
log \"$id [$side] downloading jar\"\n\
if download_atomic \"$dl_url\" \"$jar_dest\" \"$dl_sha1\"; then log \"$id [$side] download complete\"; else rm -f \"$jar_dest\"; log \"$id [$side] download failed\"; fi\n\
done\n\
SH\n\
chmod +x /app/worker.sh; \
\
# ── Decompile worker ───────────────────────────────────────────────────────────
# Layout per version/side:
# /data/minecraft/{client|server}/{id}/
# {id}.json <- version metadata
# {client|server}.jar
# src/ <- vineflower output (java source tree)
# .decompiled <- stamp: written only after a clean run
cat > /app/decompile_worker.sh <<'SH'\n\
#!/bin/bash\n\
set -u\n\
LOG=\"$1\"\n\
PROGRESS=\"$2\"\n\
JAR=\"$3\"\n\
log(){ printf '[%s] %s\\n' \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$*\" | tee -a \"$LOG\"; printf '%s\\n' \"$*\" > \"$PROGRESS\"; }\n\
\n\
dir=\"$(dirname \"$JAR\")\"\n\
side=\"$(basename \"$JAR\" .jar)\"\n\
id=\"$(basename \"$dir\")\"\n\
label=\"$id [$side]\"\n\
src_dir=\"$dir/src\"\n\
stamp=\"$dir/.decompiled\"\n\
\n\
# Skip if already successfully decompiled\n\
if [ -f \"$stamp\" ] && [ -d \"$src_dir\" ] && [ -n \"$(ls -A \"$src_dir\" 2>/dev/null)\" ]; then\n\
log \"$label decompile already done, skipping\"\n\
exit 0\n\
fi\n\
\n\
log \"$label starting decompile -> $src_dir\"\n\
rm -rf \"$src_dir\"\n\
mkdir -p \"$src_dir\"\n\
\n\
if java -jar /app/vineflower.jar \\\n\
--folder \\\n\
--log-level=WARN \\\n\
--thread-count=2 \\\n\
--decompile-generics=1 \\\n\
--decompile-enums=1 \\\n\
--decompile-inner=1 \\\n\
--use-lvt-names=1 \\\n\
--use-method-parameters=1 \\\n\
--override-annotation=1 \\\n\
--pattern-matching=1 \\\n\
--remove-synthetic=0 \\\n\
--remove-bridge=0 \\\n\
\"$JAR\" \"$src_dir\" >> \"$LOG\" 2>&1; then\n\
touch \"$stamp\"\n\
log \"$label decompile complete\"\n\
else\n\
log \"$label decompile FAILED (partial output left in $src_dir)\"\n\
exit 1\n\
fi\n\
SH\n\
chmod +x /app/decompile_worker.sh; \
\
# ── Main flow ──────────────────────────────────────────────────────────────────
acquire_lock; \
\
log 'Starting Minecraft artifact sync'; \
log 'Downloading global version manifest'; \
if ! download_atomic \"$MANIFEST_URL\" \"$MANIFEST_PATH\" ''; then log 'Failed to download manifest'; exit 1; fi; \
TOTAL=\"$(jq '.versions | length' \"$MANIFEST_PATH\")\"; \
log \"Manifest contains $TOTAL versions\"; \
\
log \"Phase 1/2 β€” downloading all jars (parallel jobs=$JOBS)\"; \
jq -r '.versions[] | \"\\(.id)|\\(.url)\"' \"$MANIFEST_PATH\" \
| xargs -I{} -P \"$JOBS\" bash /app/worker.sh \"$BASE\" \"$TMP\" \"$LOG\" \"$PROGRESS\" '{}'; \
\
log 'Phase 1/2 complete β€” all jars downloaded'; \
\
log \"Phase 2/2 β€” decompiling all jars with Vineflower (parallel jobs=$DECOMPILE_JOBS, 2 threads each)\"; \
find \"$BASE/client\" \"$BASE/server\" \
-mindepth 2 -maxdepth 2 \
-name '*.jar' \
! -name '*.part' \
| sort \
| xargs -I{} -P \"$DECOMPILE_JOBS\" bash /app/decompile_worker.sh \"$LOG\" \"$PROGRESS\" '{}'; \
\
log 'Phase 2/2 complete β€” all decompilation finished'; \
log 'Sync + decompile complete'; \
wait \
"]