#!/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 Переопределить пользователя для сервиса # (по умолчанию: $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 </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 <> ВНИМАНИЕ: установлена 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 < ${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}" <> Перечитываю 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 # удалить сервис"