This commit is contained in:
Marc
2025-10-02 00:29:29 +02:00
parent e69f8f5a7d
commit 1b74197680
9 changed files with 308 additions and 4 deletions

49
docker/shulker/Dockerfile Normal file
View File

@@ -0,0 +1,49 @@
# Build Shulker without modifying the submodule; run as non-root; persist config and read logs via volumes
FROM node:22-alpine AS build
WORKDIR /app
# Enable corepack so yarn is available (Node 18+)
RUN corepack enable
# Copy only dependency manifests first for better layer caching
COPY external/shulker/package.json external/shulker/yarn.lock ./external/shulker/
WORKDIR /app/external/shulker
# Install dependencies
RUN yarn install --frozen-lockfile || yarn install
# Copy source and build
COPY external/shulker/ /app/external/shulker/
RUN yarn build
FROM node:22-alpine
WORKDIR /app
# Copy built app from builder
COPY --from=build /app/external/shulker /app
# Prepare runtime directories and non-root user
RUN adduser -D -h /app -u 10001 shulker \
&& mkdir -p /data /minecraft/logs \
&& chown -R shulker:shulker /app /data /minecraft
# Copy entrypoint
COPY docker/shulker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Install helpful runtime tools
RUN apk add --no-cache jq
USER shulker
# Shulker default port
EXPOSE 8000
# Volumes: /data holds config.json, /minecraft/logs maps to the server logs
VOLUME ["/data", "/minecraft/logs"]
ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "build/index.js"]

View File

@@ -0,0 +1,90 @@
#!/bin/sh
set -e
# If no config exists in /data, seed it from the example and apply env overrides if provided
if [ ! -f /data/config.json ]; then
echo "Seeding /data/config.json from example"
cp /app/config.example.json /data/config.json
fi
# Ensure LOCAL_FILE_PATH points to the mounted logs unless explicitly overridden via env
if [ -z "${LOCAL_FILE_PATH_OVERRIDE}" ]; then
LOCAL_FILE_PATH="/minecraft/logs/latest.log"
else
LOCAL_FILE_PATH="${LOCAL_FILE_PATH_OVERRIDE}"
fi
# Apply a few common ENV overrides into config.json if provided
json_set() {
KEY="$1"; VALUE="$2"; FILE="$3"
# Use jq if available for robust edits; otherwise sed fallback
if command -v jq >/dev/null 2>&1; then
tmp=$(mktemp)
jq --arg v "$VALUE" ".[$KEY]=$VALUE" "$FILE" > "$tmp" && mv "$tmp" "$FILE" || true
else
# naive sed replace; expects simple string or number values already present
sed -i "s#\(\"$KEY\"\): .*#\\\"$KEY\\\": $VALUE,#" "$FILE" || true
fi
}
# Build a small jq script to apply overrides
if command -v jq >/dev/null 2>&1; then
jq \
--argfile cfg /data/config.json \
'.' >/dev/null 2>&1 || true
fi
# When env vars are set, write them. Values must be valid JSON literals (strings quoted).
[ -n "${DISCORD_TOKEN}" ] && jq ".DISCORD_TOKEN=\"${DISCORD_TOKEN}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
[ -n "${DISCORD_CHANNEL_ID}" ] && jq ".DISCORD_CHANNEL_ID=\"${DISCORD_CHANNEL_ID}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
[ -n "${WEBHOOK_URL}" ] && jq ".WEBHOOK_URL=\"${WEBHOOK_URL}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
[ -n "${DISCORD_MESSAGE_TEMPLATE}" ] && jq ".DISCORD_MESSAGE_TEMPLATE=\"${DISCORD_MESSAGE_TEMPLATE}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
[ -n "${DEBUG}" ] && jq ".DEBUG=${DEBUG}" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
# Default behavior: if no WEBHOOK_URL, disable webhooks; otherwise enable them.
# If USE_WEBHOOKS is explicitly set via env, it overrides this default.
if [ -z "${USE_WEBHOOKS}" ]; then
if jq -e '.WEBHOOK_URL and (.WEBHOOK_URL | length) > 0' /data/config.json >/dev/null 2>&1; then
jq ".USE_WEBHOOKS=true" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json
else
jq ".USE_WEBHOOKS=false" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json
fi
else
case "${USE_WEBHOOKS}" in
[Tt][Rr][Uu][Ee]|1|yes|on) jq ".USE_WEBHOOKS=true" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json ;;
[Ff][Aa][Ll][Ss][Ee]|0|no|off) jq ".USE_WEBHOOKS=false" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json ;;
esac
fi
# Customize server identity shown in Discord when posting server messages via webhook
[ -n "${SERVER_NAME}" ] && jq ".SERVER_NAME=\"${SERVER_NAME}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
[ -n "${SERVER_IMAGE}" ] && jq ".SERVER_IMAGE=\"${SERVER_IMAGE}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
# RCON configuration overrides
[ -n "${MINECRAFT_SERVER_RCON_IP}" ] && jq ".MINECRAFT_SERVER_RCON_IP=\"${MINECRAFT_SERVER_RCON_IP}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
if [ -n "${MINECRAFT_SERVER_RCON_PORT}" ]; then
case "${MINECRAFT_SERVER_RCON_PORT}" in
''|*[!0-9]*) jq ".MINECRAFT_SERVER_RCON_PORT=\"${MINECRAFT_SERVER_RCON_PORT}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json ;;
*) jq ".MINECRAFT_SERVER_RCON_PORT=${MINECRAFT_SERVER_RCON_PORT}" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json ;;
esac
fi
[ -n "${MINECRAFT_SERVER_RCON_PASSWORD}" ] && jq ".MINECRAFT_SERVER_RCON_PASSWORD=\"${MINECRAFT_SERVER_RCON_PASSWORD}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
# Minecraft output formatting overrides
if [ -n "${MINECRAFT_TELLRAW_DOESNT_EXIST}" ]; then
case "${MINECRAFT_TELLRAW_DOESNT_EXIST}" in
[Tt][Rr][Uu][Ee]|1|yes|on) jq ".MINECRAFT_TELLRAW_DOESNT_EXIST=true" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json ;;
[Ff][Aa][Ll][Ss][Ee]|0|no|off) jq ".MINECRAFT_TELLRAW_DOESNT_EXIST=false" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json ;;
esac
fi
[ -n "${MINECRAFT_TELLRAW_TEMPLATE}" ] && jq ".MINECRAFT_TELLRAW_TEMPLATE=\"${MINECRAFT_TELLRAW_TEMPLATE}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
[ -n "${MINECRAFT_TELLRAW_DOESNT_EXIST_SAY_TEMPLATE}" ] && jq ".MINECRAFT_TELLRAW_DOESNT_EXIST_SAY_TEMPLATE=\"${MINECRAFT_TELLRAW_DOESNT_EXIST_SAY_TEMPLATE}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json || true
# Always set IS_LOCAL_FILE true and path to mounted logs unless overridden explicitly via env IS_LOCAL_FILE
if [ -z "${IS_LOCAL_FILE}" ]; then IS_LOCAL_FILE=true; fi
jq ".IS_LOCAL_FILE=${IS_LOCAL_FILE} | .LOCAL_FILE_PATH=\"${LOCAL_FILE_PATH}\"" /data/config.json > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json
# Link config into app directory where shulker expects it
ln -sf /data/config.json /app/config.json
exec "$@"