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

27
.env Normal file
View File

@@ -0,0 +1,27 @@
# Copy this file to .env and fill in your values
# Discord bot token and channel
DISCORD_TOKEN=MTA3MTEwMzcwMDAyMzY0ODMyMA.GIlFkn.o10LKcpjlaLDvVsgF_WKZhz5ykPYf1KEds_g3I
DISCORD_CHANNEL_ID=1247178901004877916
# Optional if using webhooks
WEBHOOK_URL=
# Display name and avatar for server-origin messages when using webhooks
SERVER_NAME=TFMC
# Optional image URL to use as the avatar for server messages
# SERVER_IMAGE=https://example.com/server-icon.png
# If logs are mounted, keep true; set to false to use remote webhook mode
IS_LOCAL_FILE=true
# Path on host to the Minecraft server logs directory containing latest.log
MC_LOGS_DIR=./data/logs
# Optionally override the log path inside the container
# LOCAL_FILE_PATH_OVERRIDE=/minecraft/logs/latest.log
# RCON connection to send Discord -> Minecraft messages
# Ensure server.properties has enable-rcon=true and set password/port
# Set IP to the Minecraft server host or container name on the Docker network
MINECRAFT_SERVER_RCON_IP=mc
MINECRAFT_SERVER_RCON_PORT=25575
MINECRAFT_SERVER_RCON_PASSWORD=509dbd71b996df2bd575d43b

27
.env.example Normal file
View File

@@ -0,0 +1,27 @@
# Copy this file to .env and fill in your values
# Discord bot token and channel
DISCORD_TOKEN=
DISCORD_CHANNEL_ID=
# Optional if using webhooks
WEBHOOK_URL=
# Display name and avatar for server-origin messages when using webhooks
SERVER_NAME=Shulker
# Optional image URL to use as the avatar for server messages
# SERVER_IMAGE=https://example.com/server-icon.png
# If logs are mounted, keep true; set to false to use remote webhook mode
IS_LOCAL_FILE=true
# Path on host to the Minecraft server logs directory containing latest.log
MC_LOGS_DIR=./data/logs
# Optionally override the log path inside the container
# LOCAL_FILE_PATH_OVERRIDE=/minecraft/logs/latest.log
# RCON connection to send Discord -> Minecraft messages
# Ensure server.properties has enable-rcon=true and set password/port
# Set IP to the Minecraft server host or container name on the Docker network
MINECRAFT_SERVER_RCON_IP=127.0.0.1
MINECRAFT_SERVER_RCON_PORT=25575
MINECRAFT_SERVER_RCON_PASSWORD=

View File

@@ -21,3 +21,55 @@ Website for TFMC (teknisk fysiks minecraft server)
- Maybe use integration with itgz, and get variables from those servers? via for example docker sock? or would that be to insecure?
[] Maybe integrate chat with discord via rcon?
- Base on existing project? This one exist and could maybe be useful? https://github.com/destruc7i0n/shulker
## Shulker Discord-Minecraft Bridge (Dockerized)
This repo includes [Shulker](https://github.com/destruc7i0n/shulker) as a Git submodule under `external/shulker`, plus a Docker setup so you can run it and mount your Minecraft logs as a volume.
### Prereqs
- Docker and Docker Compose installed
- A Discord bot token and (optionally) a Discord channel webhook
- Your Minecraft server configured with rcon and accessible logs
### First-time setup
1. Initialize and update submodules:
- git submodule update --init --recursive
2. Configure environment variables (create a `.env` in repo root or pass via CLI):
- DISCORD_TOKEN=... (required)
- DISCORD_CHANNEL_ID=... (required if not using webhooks only)
- WEBHOOK_URL=... (if using webhooks)
- MC_LOGS_DIR=/absolute/path/to/your/minecraft/logs (defaults to `./Worlds/tfmc23-24/logs`)
- IS_LOCAL_FILE=true to tail logs directly (recommended when logs are mounted)
### Run
Start the service (from repo root):
```
docker compose up -d --build shulker
```
On first run, `/data/config.json` will be created from `config.example.json`. The container will symlink it to `/app/config.json` and set `IS_LOCAL_FILE` and `LOCAL_FILE_PATH` to `/minecraft/logs/latest.log` by default. You can edit `config.json` by entering the container or by stopping the container and editing the file in the named volume.
Expose the Shulker webhook on port 8000 if you want to send remote hooks instead of tailing logs. For remote setup, set `IS_LOCAL_FILE=false` and follow the command in Shulker's README to pipe log lines to the webhook.
### Volumes
- `shulker_data` named volume stores `config.json`.
- `${MC_LOGS_DIR}` (host) is mounted read-only at `/minecraft/logs` (container). Ensure this path contains `latest.log`.
### Updating Shulker
Shulker is a submodule:
```
git submodule update --remote external/shulker
```
Rebuild the image after updating:
```
docker compose build shulker && docker compose up -d shulker
```
### Notes
- Node 16+ is required; the Docker image uses Node 18 Alpine.
- If your server is modded, you may need to adjust `REGEX_SERVER_PREFIX` per Shulkers FAQ.
- For RCON features, set `MINECRAFT_SERVER_RCON_*` in `config.json` and ensure your server has rcon enabled.

5
data/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Ignore everything in this directory
*
# But keep this file so the directory exists in the repo
!.gitignore

58
docker-compose.yml Normal file
View File

@@ -0,0 +1,58 @@
version: "3.8"
services:
shulker:
build:
context: .
dockerfile: docker/shulker/Dockerfile
container_name: shulker
restart: unless-stopped
environment:
# Provide these via environment or .env file
- DISCORD_TOKEN=${DISCORD_TOKEN}
- DISCORD_CHANNEL_ID=${DISCORD_CHANNEL_ID}
- WEBHOOK_URL=${WEBHOOK_URL}
- SERVER_NAME=${SERVER_NAME:-Shulker}
- SERVER_IMAGE=${SERVER_IMAGE}
- DISCORD_MESSAGE_TEMPLATE=${DISCORD_MESSAGE_TEMPLATE}
- DEBUG=${DEBUG}
- ALLOW_USER_MENTIONS=${ALLOW_USER_MENTIONS}
- ALLOW_HERE_EVERYONE_MENTIONS=${ALLOW_HERE_EVERYONE_MENTIONS}
- MINECRAFT_SERVER_RCON_IP=${MINECRAFT_SERVER_RCON_IP}
- MINECRAFT_SERVER_RCON_PORT=${MINECRAFT_SERVER_RCON_PORT}
- MINECRAFT_SERVER_RCON_PASSWORD=${MINECRAFT_SERVER_RCON_PASSWORD}
- MINECRAFT_TELLRAW_DOESNT_EXIST=${MINECRAFT_TELLRAW_DOESNT_EXIST}
- MINECRAFT_TELLRAW_TEMPLATE=${MINECRAFT_TELLRAW_TEMPLATE}
- MINECRAFT_TELLRAW_DOESNT_EXIST_SAY_TEMPLATE=${MINECRAFT_TELLRAW_DOESNT_EXIST_SAY_TEMPLATE}
# Set to false if using remote hook mode
- IS_LOCAL_FILE=${IS_LOCAL_FILE:-true}
# Optionally override the log path inside the container
- LOCAL_FILE_PATH_OVERRIDE=${LOCAL_FILE_PATH_OVERRIDE}
volumes:
# Persist config.json in a named volume
- shulker_data:/data
# Map your host/server Minecraft logs into the container
- ${MC_LOGS_DIR:-./data/logs}:/minecraft/logs:ro
ports:
- "8000:8000"
mc:
image: itzg/minecraft-server
tty: true
stdin_open: true
ports:
- "25565:25565"
environment:
EULA: "TRUE"
ENABLE_RCON: "true"
RCON_PASSWORD: ${MINECRAFT_SERVER_RCON_PASSWORD}
RCON_PORT: ${MINECRAFT_SERVER_RCON_PORT}
# Ensure env values are written into server.properties on each start
OVERRIDE_SERVER_PROPERTIES: "true"
volumes:
# attach the relative directory 'data' to the container's /data path
- ./data:/data
volumes:
shulker_data:
driver: local

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

View File

@@ -1,4 +0,0 @@
{
"id" : "edc64ca33ed24de1852b11051edc82e5",
"name" : "Millerman03"
}

0
requirements.txt Normal file
View File