#!/usr/bin/env bash # Плановый перезапуск Minecraft-сервера с предупреждениями в чат (RCON) и # корректной остановкой (save-all flush + stop). После stop unit systemd # сам не поднимется при Restart=on-failure — скрипт делает systemctl start. # # Требования: # - В server.properties: enable-rcon=true, rcon.password=..., rcon.port=... # - RCON-клиент в PATH: сначала ищется mcrcon, иначе rcon (github.com/gorcon/rcon-cli) # - Права на systemctl для сервиса (удобнее всего — crontab root) # # Установка mcrcon, если в apt пакета нет (Linux x86_64, статический бинарник): # curl -fsSLO https://github.com/Tiiffi/mcrcon/releases/download/v0.7.2/mcrcon-0.7.2-linux-x86-64-static.zip # unzip -o mcrcon-0.7.2-linux-x86-64-static.zip # sudo install -m755 mcrcon-0.7.2-linux-x86-64-static/mcrcon /usr/local/bin/ # # Сборка из исходников: # sudo apt install -y git build-essential # git clone --depth 1 https://github.com/Tiiffi/mcrcon.git && cd mcrcon && make && sudo install -m755 mcrcon /usr/local/bin/ # # Расписание (каждые 6 часов: 00:00, 06:00, 12:00, 18:00): # crontab -e # 0 */6 * * * /opt/PupaMLupa/Server/restart-scheduled.sh >>/var/log/mc-restart.log 2>&1 # # Переменные окружения (необязательно): # MINECRAFT_SERVICE — имя unit (по умолчанию: minecraft) # RCON_HOST — хост RCON (по умолчанию: 127.0.0.1) # STOP_WAIT_SEC — макс. ожидание выхода процесса после stop (по умолчанию: 180) # RCON_TIMEOUT — таймаут для rcon (gorcon), напр. 60s (по умолчанию: 60s) set -euo pipefail # cron часто даёт урезанный PATH — mcrcon обычно в /usr/bin или /usr/local/bin export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" MINECRAFT_SERVICE="${MINECRAFT_SERVICE:-minecraft}" RCON_HOST="${RCON_HOST:-127.0.0.1}" STOP_WAIT_SEC="${STOP_WAIT_SEC:-180}" RCON_TIMEOUT="${RCON_TIMEOUT:-60s}" RCON_CLIENT="" LOCK_FILE="${SCRIPT_DIR}/.restart-scheduled.lock" PROPS="${SCRIPT_DIR}/server.properties" log() { echo "[$(date -Iseconds)] $*" } die() { log "ОШИБКА: $*" >&2 exit 1 } read_prop() { local key="$1" [[ -f "${PROPS}" ]] || die "нет файла ${PROPS}" grep -E "^[[:space:]]*${key}[[:space:]]*=" "${PROPS}" 2>/dev/null | head -n1 \ | cut -d= -f2- | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' } systemctl_wrap() { if [[ "${EUID:-0}" -eq 0 ]]; then systemctl "$@" elif command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then sudo -n systemctl "$@" else systemctl "$@" fi } rcon_exec() { local cmd="$1" if [[ "${RCON_CLIENT}" == "mcrcon" ]]; then mcrcon -H "${RCON_HOST}" -P "${RCON_PORT}" -p "${RCON_PASSWORD}" "${cmd}" else # gorcon/rcon-cli: команда передаётся словами, как в консоли сервера (say …, save-all flush, …) # shellcheck disable=SC2086 rcon -a "${RCON_HOST}:${RCON_PORT}" -p "${RCON_PASSWORD}" -T "${RCON_TIMEOUT}" ${cmd} fi } rcon_say() { rcon_exec "say $1" } # --- блокировка: не запускать два перезапуска одновременно --- exec 9>"${LOCK_FILE}" if ! flock -n 9; then log "уже идёт другой запуск (flock ${LOCK_FILE}) — выход." exit 0 fi if command -v mcrcon >/dev/null 2>&1; then RCON_CLIENT="mcrcon" elif command -v rcon >/dev/null 2>&1; then RCON_CLIENT="rcon" else die "нужен RCON-клиент: mcrcon или rcon (https://github.com/gorcon/rcon-cli/releases). См. комментарии в начале этого скрипта — статический mcrcon для x86_64 или сборка из git." fi log "RCON-клиент: ${RCON_CLIENT}" if ! systemctl_wrap is-active --quiet "${MINECRAFT_SERVICE}"; then log "сервис ${MINECRAFT_SERVICE} не active — перезапуск не выполняется." exit 0 fi rc_en="$(read_prop enable-rcon)" [[ "${rc_en,,}" == "true" ]] || die "в server.properties включите enable-rcon=true" RCON_PORT="$(read_prop rcon.port)" RCON_PASSWORD="$(read_prop rcon.password)" [[ -n "${RCON_PORT}" ]] || die "не задан rcon.port" [[ -n "${RCON_PASSWORD}" ]] || die "не задан rcon.password" # Проверка RCON rcon_exec "list" >/dev/null || die "RCON не отвечает (хост ${RCON_HOST}, порт ${RCON_PORT})" log "Начало планового перезапуска (${MINECRAFT_SERVICE})." rcon_say "[Сервер] Плановый перезапуск через 10 минут!" sleep 300 rcon_say "[Сервер] Перезапуск через 5 минут!" sleep 240 rcon_say "[Сервер] Перезапуск через 1 минуту!" sleep 30 rcon_say "[Сервер] Перезапуск через 30 секунд!" sleep 25 for n in 5 4 3 2 1; do rcon_say "[Сервер] $n" sleep 1 done log "Сохранение мира и остановка..." rcon_exec "save-all flush" || true sleep 3 rcon_exec "stop" _waited=0 while systemctl_wrap is-active --quiet "${MINECRAFT_SERVICE}"; do if ((_waited >= STOP_WAIT_SEC)); then die "таймаут ${STOP_WAIT_SEC}s: сервис всё ещё active после stop" fi sleep 2 _waited=$((_waited + 2)) done log "Процесс остановлен, запускаю сервис..." systemctl_wrap start "${MINECRAFT_SERVICE}" sleep 2 if systemctl_wrap is-active --quiet "${MINECRAFT_SERVICE}"; then log "Готово: ${MINECRAFT_SERVICE} снова active." else die "systemctl start не поднял сервис — проверьте: systemctl status ${MINECRAFT_SERVICE}" fi