Files
PupaMLupa/Server/install-service.sh
T

255 lines
11 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
# install-service.sh
# Создаёт systemd-сервис для запуска Minecraft сервера через ./run.sh
# и сразу его поднимает. Должен быть запущен от root через sudo.
#
# Сервис будет работать от того же пользователя, который вызвал sudo
# (т.е. от $SUDO_USER): не нужно создавать отдельного юзера, git pull и
# редактирование конфигов работают как обычно.
#
# ВАЖНО: перед установкой сервиса один раз запустите сервер вручную:
# ./start.sh
# start.sh сам поставит Java (если не установлена), скачает и развернёт
# NeoForge, сгенерирует user_jvm_args.txt, скачает server.jar и спросит
# про EULA. После того как сервер успешно запустился (увидите 'Done!'),
# остановите его (команда 'stop' в консоли или Ctrl+C) и запускайте
# этот скрипт. Сервис будет использовать run.sh, который уже не делает
# никаких интерактивных шагов — просто запускает java.
#
# Использование:
# sudo ./install-service.sh
# sudo ./install-service.sh --name minecraft
# sudo ./install-service.sh --uninstall
#
# Флаги:
# --name <имя> Имя systemd-сервиса (по умолчанию: minecraft)
# --user <user> Переопределить пользователя для сервиса
# (по умолчанию: $SUDO_USER)
# --dir <путь> Каталог сервера (по умолчанию: каталог скрипта)
# --uninstall Остановить, выключить и удалить сервис
# -h | --help Показать справку
set -euo pipefail
SERVICE_NAME="minecraft"
SERVICE_USER=""
SERVER_DIR="$(cd "$(dirname "$0")" && pwd)"
UNINSTALL=0
print_help() {
sed -n '2,32p' "$0" | sed 's/^# \{0,1\}//'
}
err() {
echo "Ошибка: $*" >&2
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--name) SERVICE_NAME="$2"; shift 2 ;;
--user) SERVICE_USER="$2"; shift 2 ;;
--dir) SERVER_DIR="$2"; shift 2 ;;
--uninstall) UNINSTALL=1; shift ;;
-h|--help) print_help; exit 0 ;;
*) err "Неизвестный аргумент: $1 (используйте --help)" ;;
esac
done
# 1. Проверка прав root
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
err "скрипт должен быть запущен от root. Используйте: sudo $0 $*"
fi
# 2. Проверка systemd
if ! command -v systemctl >/dev/null 2>&1; then
err "systemctl не найден. Этот скрипт работает только на системах с systemd."
fi
UNIT_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
# Удаление сервиса
if [[ "${UNINSTALL}" -eq 1 ]]; then
echo ">> Останавливаю и удаляю сервис ${SERVICE_NAME}..."
systemctl stop "${SERVICE_NAME}.service" 2>/dev/null || true
systemctl disable "${SERVICE_NAME}.service" 2>/dev/null || true
if [[ -f "${UNIT_FILE}" ]]; then
rm -f "${UNIT_FILE}"
echo " Удалён ${UNIT_FILE}"
fi
systemctl daemon-reload
systemctl reset-failed "${SERVICE_NAME}.service" 2>/dev/null || true
echo ">> Готово. Сервис ${SERVICE_NAME} удалён."
exit 0
fi
# 3. Определение пользователя сервиса
if [[ -z "${SERVICE_USER}" ]]; then
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then
SERVICE_USER="${SUDO_USER}"
else
err "не удалось определить пользователя. Запустите через sudo от обычного юзера, либо укажите --user <имя>."
fi
fi
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
err "пользователь '${SERVICE_USER}' не существует."
fi
SERVICE_GROUP="$(id -gn "${SERVICE_USER}")"
SERVICE_HOME="$(getent passwd "${SERVICE_USER}" | cut -d: -f6)"
echo ">> Сервис будет работать от: ${SERVICE_USER}:${SERVICE_GROUP}"
# 3a. Поиск Java для systemd. systemd не загружает ~/.bashrc, поэтому
# если Java стоит только через Jabba (~/.jabba/), unit её не увидит.
# Проверяем по очереди: Jabba у пользователя сервиса -> системный PATH.
JAVA_HOME_PATH=""
JAVA_BIN_DIR=""
if [[ -d "${SERVICE_HOME}/.jabba/jdk" ]]; then
JABBA_JDK="$(ls -1 "${SERVICE_HOME}/.jabba/jdk/" 2>/dev/null | sort -V | tail -n1 || true)"
if [[ -n "${JABBA_JDK}" ]] \
&& [[ -x "${SERVICE_HOME}/.jabba/jdk/${JABBA_JDK}/bin/java" ]]; then
JAVA_HOME_PATH="${SERVICE_HOME}/.jabba/jdk/${JABBA_JDK}"
JAVA_BIN_DIR="${JAVA_HOME_PATH}/bin"
echo ">> Найдена Java через Jabba: ${JAVA_BIN_DIR}/java"
fi
fi
if [[ -z "${JAVA_BIN_DIR}" ]] && command -v java >/dev/null 2>&1; then
SYSTEM_JAVA="$(command -v java)"
echo ">> Найдена системная Java: ${SYSTEM_JAVA}"
# Системную Java systemd увидит сам — Environment=PATH прописывать не нужно.
fi
if [[ -z "${JAVA_BIN_DIR}" ]] && ! command -v java >/dev/null 2>&1; then
echo ">> ВНИМАНИЕ: java не найдена ни в PATH, ни в ~${SERVICE_USER}/.jabba." >&2
echo " Сервис, скорее всего, упадёт с 'java: command not found'." >&2
echo " Поставьте: sudo apt install -y openjdk-21-jre-headless" >&2
fi
# 4. Проверка содержимого каталога сервера
[[ -d "${SERVER_DIR}" ]] || err "каталог сервера не найден: ${SERVER_DIR}"
[[ -f "${SERVER_DIR}/run.sh" ]] || err "не найден ${SERVER_DIR}/run.sh"
chmod +x "${SERVER_DIR}/run.sh" 2>/dev/null || true
# 5. Проверка готовности сервера (артефакты, которые создаёт start.sh)
need_first_run() {
cat >&2 <<EOF
============================ ВНИМАНИЕ ============================
Сервер ещё не подготовлен к запуску через run.sh:
$1
run.sh — это лёгкий лаунчер, он только запускает java и не умеет
скачивать NeoForge / Java / server.jar и спрашивать EULA. Поэтому
ПЕРЕД установкой сервиса нужно один раз запустить сервер вручную:
cd ${SERVER_DIR}
./start.sh
start.sh сам:
- поставит Java (или подскажет, как поставить системно);
- скачает и развернёт NeoForge в libraries/;
- сгенерирует user_jvm_args.txt;
- скачает server.jar;
- спросит про EULA.
Когда сервер успешно поднимется (в логе появится 'Done (Xs)!'),
остановите его: в консоли наберите 'stop' и нажмите Enter
(или Ctrl+C). После этого запустите этот скрипт повторно:
sudo ./install-service.sh
====================================================================
EOF
exit 1
}
# Файл NeoForge unix_args.txt — самый надёжный признак, что start.sh отработал.
NEOFORGE_ARGS_FILE="$(ls -1 "${SERVER_DIR}"/libraries/net/neoforged/neoforge/*/unix_args.txt 2>/dev/null | head -n1 || true)"
if [[ -z "${NEOFORGE_ARGS_FILE}" ]]; then
need_first_run "не найден libraries/net/neoforged/neoforge/<версия>/unix_args.txt — NeoForge не развёрнут."
fi
[[ -f "${SERVER_DIR}/user_jvm_args.txt" ]] \
|| need_first_run "не найден user_jvm_args.txt — start.sh не сгенерировал JVM-аргументы."
[[ -f "${SERVER_DIR}/server.jar" && -s "${SERVER_DIR}/server.jar" ]] \
|| need_first_run "server.jar отсутствует или пустой — start.sh не докачал лаунчер."
EULA_FILE="${SERVER_DIR}/eula.txt"
if [[ ! -f "${EULA_FILE}" ]] || ! grep -qE '^\s*eula\s*=\s*true' "${EULA_FILE}"; then
need_first_run "${EULA_FILE} отсутствует или eula=false — без принятия EULA сервер не стартует."
fi
# 6. Предупреждение про /home — типичная причина status=200/CHDIR.
if [[ "${SERVER_DIR}" == /home/* ]]; then
cat >&2 <<EOF
>> ВНИМАНИЕ: каталог сервера находится в /home/. Для systemd
рекомендуется переносить сервисы в /opt (или /srv).
ProtectHome будет ослаблен в unit-файле.
EOF
fi
# 7. Генерация systemd unit-файла
if [[ "${SERVER_DIR}" == /home/* ]]; then
PROTECT_HOME_LINE="ProtectHome=false"
else
PROTECT_HOME_LINE="ProtectHome=read-only"
fi
echo ">> Создаю ${UNIT_FILE}..."
cat > "${UNIT_FILE}" <<EOF
[Unit]
Description=Minecraft Server (${SERVICE_NAME})
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=${SERVICE_USER}
Group=${SERVICE_GROUP}
WorkingDirectory=${SERVER_DIR}
$( [[ -n "${JAVA_BIN_DIR}" ]] && echo "Environment=PATH=${JAVA_BIN_DIR}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" )
$( [[ -n "${JAVA_HOME_PATH}" ]] && echo "Environment=JAVA_HOME=${JAVA_HOME_PATH}" )
ExecStart=/usr/bin/env bash ${SERVER_DIR}/run.sh nogui
Restart=on-failure
RestartSec=10s
TimeoutStopSec=120
KillSignal=SIGINT
StandardOutput=journal
StandardError=journal
# Безопасность
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
${PROTECT_HOME_LINE}
ReadWritePaths=${SERVER_DIR}
[Install]
WantedBy=multi-user.target
EOF
chmod 644 "${UNIT_FILE}"
# 8. Активация сервиса
echo ">> Перечитываю systemd, включаю и запускаю сервис..."
systemctl daemon-reload
systemctl enable "${SERVICE_NAME}.service"
systemctl restart "${SERVICE_NAME}.service"
sleep 2
echo ""
echo "===================================================================="
systemctl --no-pager --full status "${SERVICE_NAME}.service" || true
echo "===================================================================="
echo ""
echo "Готово. Полезные команды:"
echo " sudo systemctl status ${SERVICE_NAME}"
echo " sudo systemctl stop ${SERVICE_NAME}"
echo " sudo systemctl restart ${SERVICE_NAME}"
echo " sudo journalctl -u ${SERVICE_NAME} -f # живые логи"
echo " sudo $0 --uninstall # удалить сервис"