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