Files
PupaMLupa/Server/install-service.sh
T

229 lines
9.4 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 сервера (через ./start.sh)
# и сразу его поднимает. Должен быть запущен от root через sudo.
#
# Сервис будет работать от того же пользователя, который вызвал sudo
# (т.е. от $SUDO_USER). Это значит:
# - не нужно создавать отдельного юзера и делать chown;
# - git pull / редактирование конфигов работают как обычно, без sudo;
# - домашний каталог пользователя доступен сервису.
#
# Использование:
# sudo ./install-service.sh
# sudo ./install-service.sh --name minecraft --java-args "-Xms4G -Xmx8G"
# sudo ./install-service.sh --uninstall
#
# Флаги:
# --name <имя> Имя systemd-сервиса (по умолчанию: minecraft)
# --user <user> Переопределить пользователя для сервиса
# (по умолчанию: $SUDO_USER)
# --dir <путь> Каталог сервера (по умолчанию: каталог скрипта)
# --java-args <строка> Дополнительные JVM-аргументы (напр. "-Xms4G -Xmx8G")
# --uninstall Остановить, выключить и удалить сервис
# -h | --help Показать справку
set -euo pipefail
SERVICE_NAME="minecraft"
SERVICE_USER=""
SERVER_DIR="$(cd "$(dirname "$0")" && pwd)"
JAVA_ARGS=""
UNINSTALL=0
print_help() {
sed -n '2,25p' "$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 ;;
--java-args) JAVA_ARGS="$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}")"
echo ">> Сервис будет работать от: ${SERVICE_USER}:${SERVICE_GROUP}"
# 4. Проверка содержимого каталога сервера
[[ -d "${SERVER_DIR}" ]] || err "каталог сервера не найден: ${SERVER_DIR}"
[[ -f "${SERVER_DIR}/start.sh" ]] || err "не найден ${SERVER_DIR}/start.sh"
# 4a. Предупреждение про /home — типичная причина status=200/CHDIR.
# ProtectHome=read-only блокирует доступ к /home. Если сервер
# в /home — ослабим защиту в unit-файле (см. ниже).
if [[ "${SERVER_DIR}" == /home/* ]]; then
cat >&2 <<EOF
============================ ВНИМАНИЕ ============================
Каталог сервера находится в /home/. Для systemd рекомендуется
переносить такие сервисы в /opt (или /srv): это удобнее с точки
зрения прав и sandbox-настроек.
Скрипт продолжит работу — ProtectHome будет ослаблен в unit-файле.
====================================================================
EOF
fi
chmod +x "${SERVER_DIR}/start.sh" "${SERVER_DIR}/run.sh" 2>/dev/null || true
# 4b. Проверка Java. start.sh иначе попытается интерактивно поставить
# Jabba и упадёт в systemd с "User did not agree to Jabba installation".
REQUIRED_JAVA_VERSION="$(grep -E '^RECOMMENDED_JAVA_VERSION=' "${SERVER_DIR}/variables.txt" 2>/dev/null \
| cut -d= -f2 | tr -d '"' | head -n1)"
REQUIRED_JAVA_VERSION="${REQUIRED_JAVA_VERSION:-21}"
if ! command -v java >/dev/null 2>&1; then
cat >&2 <<EOF
============================ ВНИМАНИЕ ============================
Java не найдена в системе. Сначала установите Java ${REQUIRED_JAVA_VERSION},
затем запустите этот скрипт повторно.
Рекомендуемый способ (системный пакет, виден всем пользователям):
sudo apt update
sudo apt install -y openjdk-${REQUIRED_JAVA_VERSION}-jre-headless
Альтернатива — скрипт ./install_java.sh (использует Jabba, ставит
Java в \$HOME пользователя). Запускайте его от того же юзера, под
которым будет работать сервис (${SERVICE_USER}), без sudo.
====================================================================
EOF
err "java не найдена в PATH. Установите Java ${REQUIRED_JAVA_VERSION} и повторите."
fi
JAVA_MAJOR="$(java -version 2>&1 | awk -F\" '/version/ {print $2}' | cut -d. -f1)"
if [[ "${JAVA_MAJOR}" == "1" ]]; then
JAVA_MAJOR="$(java -version 2>&1 | awk -F\" '/version/ {print $2}' | cut -d. -f2)"
fi
if [[ "${JAVA_MAJOR}" != "${REQUIRED_JAVA_VERSION}" ]]; then
echo ">> ВНИМАНИЕ: установлена Java ${JAVA_MAJOR}, ожидалась ${REQUIRED_JAVA_VERSION}." >&2
echo " Сервер может не стартовать. Рекомендуется openjdk-${REQUIRED_JAVA_VERSION}-jre-headless." >&2
fi
# 5. Проверка EULA
EULA_FILE="${SERVER_DIR}/eula.txt"
if [[ ! -f "${EULA_FILE}" ]] || ! grep -qE '^\s*eula\s*=\s*true' "${EULA_FILE}"; then
cat >&2 <<EOF
Внимание: ${EULA_FILE} отсутствует или eula=false.
В systemd нет интерактивного stdin, поэтому сервер не сможет спросить согласие.
Подтвердите EULA вручную перед стартом сервиса:
echo 'eula=true' > ${EULA_FILE}
Скрипт продолжит и создаст unit, но сервис стартанёт только после принятия EULA.
EOF
fi
# 6. Генерация 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}
Environment=WAIT_FOR_USER_INPUT=false
$( [[ -n "${JAVA_ARGS}" ]] && echo "Environment=JAVA_ARGS=${JAVA_ARGS}" )
ExecStart=/usr/bin/env bash ${SERVER_DIR}/start.sh
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}"
# 7. Активация сервиса
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 # удалить сервис"