minecraft-ntfy-notify/mc-ntfy-notify.v1.sh

222 lines
6.7 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#!/bin/bash
# Beschreibung: Minecraft → ntfy Notifier (Join/Leave + optional Up/Down) mit Cron-safe Locking
# Autor: Patrick Asmus
# Web: https://www.cleveradmin.de
# Repository: https://git.techniverse.net/scriptos/minecraft-ntfy-notify
# Version: 1.3
# Datum: 18.09.2025
# Modifikation: Cron-sicheres Locking (Lockdir+PID), Running-Mark, Cleanup via trap
#####################################################
set -euo pipefail
# Robustes PATH für Cron
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [[ -f "${SCRIPT_DIR}/.env" ]]; then
set -a
. "${SCRIPT_DIR}/.env"
set +a
fi
# ===== Konfiguration via ENV =====
MC_HOST="${MC_HOST:-127.0.0.1}"
RCON_PORT="${RCON_PORT:-25575}"
RCON_PASSWORD="${RCON_PASSWORD:-changeme}"
POLL_SECONDS="${POLL_SECONDS:-10}"
ANNOUNCE_SERVER_UPDOWN="${ANNOUNCE_SERVER_UPDOWN:-true}"
# ntfy
NTFY_SERVER="${NTFY_SERVER:-}"
NTFY_TOPIC="${NTFY_TOPIC:-}"
NTFY_TOKEN="${NTFY_TOKEN:-}"
NTFY_TITLE_PREFIX="${NTFY_TITLE_PREFIX:-Minecraft}"
NTFY_TAGS_BASE="${NTFY_TAGS_BASE:-minecraft}"
NTFY_PRIORITY_JOIN="${NTFY_PRIORITY_JOIN:-3}"
NTFY_PRIORITY_LEAVE="${NTFY_PRIORITY_LEAVE:-3}"
NTFY_PRIORITY_UP="${NTFY_PRIORITY_UP:-4}"
NTFY_PRIORITY_DOWN="${NTFY_PRIORITY_DOWN:-5}"
NTFY_MARKDOWN="${NTFY_MARKDOWN:-false}"
# State (standardmäßig im Script-Ordner, root-frei)
STATE_DIR="${STATE_DIR:-${SCRIPT_DIR}/state}"
STATE_PLAYERS="${STATE_DIR}/players.prev"
STATE_UP="${STATE_DIR}/server_up.prev"
# Lock & Running-Mark (pro MC_HOST:RCON_PORT:NTFY_TOPIC eindeutig)
# -> erlaubt mehrere parallele Instanzen für verschiedene Server/Topics
LOCK_KEY="$(printf '%s' "${MC_HOST}_${RCON_PORT}_${NTFY_TOPIC}" | tr -c 'A-Za-z0-9._-' '_')"
RUN_DIR="${RUN_DIR:-${STATE_DIR}}"
LOCK_DIR="${RUN_DIR}/lock.${LOCK_KEY}"
PID_FILE="${LOCK_DIR}/pid"
RUN_MARK="${RUN_DIR}/running.${LOCK_KEY}"
# Optional Debug
DEBUG="${DEBUG:-false}"
# ===== Helpers =====
die() { echo "ERROR: $*" >&2; exit 1; }
need_bin() { command -v "$1" >/dev/null 2>&1 || die "Benötigtes Tool fehlt: $1"; }
dbg() { [[ "$DEBUG" == "true" ]] && echo "DBG: $*" >&2 || true; }
cleanup() {
# Lauf-Mark und Lock sauber entfernen
rm -f "$RUN_MARK" 2>/dev/null || true
rm -f "$PID_FILE" 2>/dev/null || true
rmdir "$LOCK_DIR" 2>/dev/null || true
}
acquire_lock() {
mkdir -p "$RUN_DIR"
# Atomarer Lock-Versuch
if mkdir "$LOCK_DIR" 2>/dev/null; then
echo "$$" > "$PID_FILE"
trap cleanup EXIT INT TERM
return 0
fi
# Lock existiert -> prüfen, ob Prozess noch lebt
if [[ -f "$PID_FILE" ]]; then
oldpid="$(cat "$PID_FILE" 2>/dev/null || true)"
if [[ -n "${oldpid:-}" ]] && kill -0 "$oldpid" 2>/dev/null; then
# Schon aktiv → leise beenden (Cron-safe)
dbg "Bereits laufend (PID $oldpid), beende."
exit 0
fi
fi
# Stale Lock entfernen und erneut versuchen
rm -rf "$LOCK_DIR" 2>/dev/null || true
if mkdir "$LOCK_DIR" 2>/dev/null; then
echo "$$" > "$PID_FILE"
trap cleanup EXIT INT TERM
return 0
fi
# Falls wir hier landen, ist wirklich etwas schief
die "Konnte Lock nicht übernehmen: ${LOCK_DIR}"
}
ntfy_notify() {
local title="$1" body="$2" tags="$3" priority="$4"
[[ -z "$NTFY_SERVER" || -z "$NTFY_TOPIC" ]] && { echo "ntfy Server/Topic fehlt skip"; return 1; }
local url="${NTFY_SERVER%/}/${NTFY_TOPIC}"
local args=(-sS -X POST "$url" -H "Title: ${title}" -H "Priority: ${priority}" -H "Tags: ${tags}")
[[ "$NTFY_MARKDOWN" == "true" ]] && args+=(-H "Markdown: yes")
[[ -n "$NTFY_TOKEN" ]] && args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
curl "${args[@]}" --data-raw "$body" >/dev/null 2>&1 || return 1
return 0
}
ensure_state() {
mkdir -p "$STATE_DIR"
touch "$STATE_PLAYERS"
[[ -f "$STATE_UP" ]] || echo "unknown" >"$STATE_UP"
}
# Strippt ANSI-Escape-Sequenzen, MC-Farbcodes (§x), CR und Steuerzeichen
sanitize() {
sed -E $'s/\x1B\\[[0-9;]*[A-Za-z]//g' \
| sed -E 's/§[0-9A-FK-ORa-fk-or]//g' \
| tr -d '\r' \
| sed -E 's/[\x00-\x1F\x7F]//g'
}
get_players() {
local out
if ! out=$(mcrcon -H "$MC_HOST" -P "$RCON_PORT" -p "$RCON_PASSWORD" "list" 2>/dev/null); then
return 1
fi
dbg "RAW: $out"
out="$(printf '%s' "$out" | sanitize)"
if ! grep -q "players online:" <<<"$out"; then
return 0
fi
local names="${out#*:}"
names="$(echo "$names" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
if [[ -z "$names" ]]; then
return 0
fi
printf '%s\n' "$names" \
| tr ',' '\n' \
| sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \
| sed '/^$/d' \
| LC_ALL=C sort -u
return 0
}
# ===== Checks & Lock =====
need_bin curl
need_bin mcrcon
[[ -n "${NTFY_SERVER}" && -n "${NTFY_TOPIC}" ]] || die "NTFY_SERVER/NTFY_TOPIC nicht gesetzt"
ensure_state
acquire_lock
echo "Starte Polling ${MC_HOST}:${RCON_PORT} → ntfy ${NTFY_SERVER}/${NTFY_TOPIC} (PID $$)"
prev_up="$(cat "$STATE_UP")"
# ===== Main Loop =====
while :; do
# Running-Mark aktualisieren (für Monitoring/„läuft“-Check)
date -Iseconds > "$RUN_MARK"
echo "$$" >> "$RUN_MARK"
tmp_players="$(mktemp)"
if get_players >"$tmp_players"; then
server_up="true"
else
server_up="false"
: >"$tmp_players"
fi
if [[ "$ANNOUNCE_SERVER_UPDOWN" == "true" && "$prev_up" != "unknown" && "$server_up" != "$prev_up" ]]; then
if [[ "$server_up" == "true" ]]; then
ntfy_notify "${NTFY_TITLE_PREFIX}: Server up" \
"Server ist wieder erreichbar (${MC_HOST}:${RCON_PORT})." \
"${NTFY_TAGS_BASE},up" "$NTFY_PRIORITY_UP" || true
else
ntfy_notify "${NTFY_TITLE_PREFIX}: Server down" \
"Server ist nicht erreichbar (${MC_HOST}:${RCON_PORT})." \
"${NTFY_TAGS_BASE},down" "$NTFY_PRIORITY_DOWN" || true
fi
fi
if [[ "$server_up" == "true" ]]; then
LC_ALL=C sort -u "$STATE_PLAYERS" -o "$STATE_PLAYERS"
joined="$(comm -13 "$STATE_PLAYERS" "$tmp_players" || true)"
left="$(comm -23 "$STATE_PLAYERS" "$tmp_players" || true)"
if [[ -n "$joined" ]]; then
while IFS= read -r name; do
[[ -z "$name" ]] && continue
ntfy_notify "${NTFY_TITLE_PREFIX}: Join" \
"Player \"${name}\" ist beigetreten." \
"${NTFY_TAGS_BASE},join" "$NTFY_PRIORITY_JOIN" || true
done <<<"$joined"
fi
if [[ -n "$left" ]]; then
while IFS= read -r name; do
[[ -z "$name" ]] && continue
ntfy_notify "${NTFY_TITLE_PREFIX}: Leave" \
"Player \"${name}\" hat den Server verlassen." \
"${NTFY_TAGS_BASE},leave" "$NTFY_PRIORITY_LEAVE" || true
done <<<"$left"
fi
mv "$tmp_players" "$STATE_PLAYERS"
else
rm -f "$tmp_players"
fi
echo -n "$server_up" >"$STATE_UP"
prev_up="$server_up"
sleep "$POLL_SECONDS"
done