#!/usr/bin/env bash # # Установка игровой ноды (локации) в один клик. # Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом. # # Нода — это машина, на которой реально крутятся игровые сервера. # Панель подключается к ней по SSH и создаёт docker-контейнер на каждый сервер. # # Что делает скрипт: # 1. Проверяет, что docker здесь вообще может работать (в контейнерных VPS — не может) # 2. Ставит docker, собирает образ debian:stretch (имя ждёт панель) # 3. Создаёт раскладку /home/cp/gameservers/files и группу gameservers # 4. Ставит MariaDB для баз игровых серверов и SteamCMD # 5. Настраивает SSH и ProFTPD так, как ожидает панель # 6. Печатает данные для подключения локации в админке # # Запуск: sudo bash install-node.sh # set -euo pipefail C_OK=$'\033[1;32m'; C_ERR=$'\033[1;31m'; C_INF=$'\033[1;36m'; C_W=$'\033[1;33m'; C_0=$'\033[0m' step() { printf '%s»%s %s\n' "$C_INF" "$C_0" "$1"; } ok() { printf ' %s✓%s %s\n' "$C_OK" "$C_0" "$1"; } warn() { printf ' %s!%s %s\n' "$C_W" "$C_0" "$1"; } die() { printf '%s✗ %s%s\n' "$C_ERR" "$1" "$C_0" >&2; exit 1; } [[ $EUID -eq 0 ]] || die "Запустите скрипт от root: sudo bash $0" SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" printf '\n%s=== Игровая нода · установка ===%s\n' "$C_INF" "$C_0" printf 'Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом\n\n' # ── 0. проверка пригодности машины ─────────────────────────────────────────── step "Проверяю, может ли эта машина запускать docker…" CAPFAIL=0 if ! unshare -Ur true 2>/dev/null; then CAPFAIL=1 warn "Ядро запрещает вложенные namespace (unshare не работает)" fi if [[ ! -e /dev/fuse && $CAPFAIL -eq 1 ]]; then warn "Нет /dev/fuse" fi if [[ $CAPFAIL -eq 1 ]]; then printf '\n%sЭТА МАШИНА НЕ ПОДХОДИТ ПОД ИГРОВУЮ НОДУ.%s\n' "$C_ERR" "$C_0" printf 'Панель разворачивает каждый игровой сервер отдельным docker-контейнером,\n' printf 'а здесь docker не запустится: это контейнерный VPS (LXC/OpenVZ или\n' printf 'аналог) без CAP_SYS_ADMIN. Rootless podman тоже не поможет — ядро\n' printf 'запрещает вложенные user namespace.\n\n' printf 'Нужен сервер с полным доступом к ядру: выделенный сервер (dedicated)\n' printf 'или KVM/VMware-виртуализация. Панель при этом может жить где угодно,\n' printf 'включая контейнерный VPS — ей docker не нужен.\n\n' read -rp "Всё равно продолжить установку? (yes/no): " GO [[ "$GO" == "yes" ]] || exit 1 else ok "Namespace доступны, docker будет работать" fi read -rp "IP этой ноды (как её увидит панель): " NODEIP [[ -n "$NODEIP" ]] || die "IP не может быть пустым" read -rp "Разрешить панели вход по SSH под root с паролем? (yes/no): " SSHROOT . /etc/os-release 2>/dev/null || true CODENAME="${VERSION_CODENAME:-bookworm}" DISTRO_ID="${ID:-debian}" step "Система: ${PRETTY_NAME:-неизвестна}" export DEBIAN_FRONTEND=noninteractive # ── 1. базовые пакеты ──────────────────────────────────────────────────────── step "Ставлю базовые пакеты…" apt-get update -qq apt-get install -y -qq curl wget ca-certificates gnupg unzip tar sudo pwgen htop \ openssh-server mariadb-server proftpd-basic lib32stdc++6 >/dev/null 2>&1 || \ apt-get install -y -qq curl wget ca-certificates gnupg unzip tar sudo pwgen htop \ openssh-server mariadb-server proftpd lib32stdc++6 >/dev/null ok "Пакеты установлены" # ── 2. docker ──────────────────────────────────────────────────────────────── step "Ставлю Docker…" if command -v docker >/dev/null 2>&1; then ok "Docker уже установлен: $(docker --version 2>/dev/null | head -1)" else install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://download.docker.com/linux/$DISTRO_ID/gpg" -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$DISTRO_ID $CODENAME stable" \ > /etc/apt/sources.list.d/docker.list apt-get update -qq apt-get install -y -qq docker-ce docker-ce-cli containerd.io >/dev/null ok "Docker установлен из официального репозитория" fi service docker start >/dev/null 2>&1 || /etc/init.d/docker start >/dev/null 2>&1 || true sleep 3 docker info >/dev/null 2>&1 || die "Docker-демон не поднялся. Без него нода не заработает" ok "Docker-демон отвечает" # ── 3. образ игровых серверов ──────────────────────────────────────────────── step "Собираю образ для игровых серверов (тег debian:stretch — его ждёт панель)…" if docker image inspect debian:stretch >/dev/null 2>&1; then ok "Образ debian:stretch уже собран" else DF="$SRC/docker/Dockerfile" [[ -f "$DF" ]] || die "Не найден $DF — распакуйте репозиторий целиком" docker build -t debian:stretch -f "$DF" "$SRC/docker" >/tmp/redl-docker-build.log 2>&1 \ || { tail -25 /tmp/redl-docker-build.log; die "Сборка образа не удалась, полный лог: /tmp/redl-docker-build.log"; } ok "Образ собран (Debian 12 + 32-битные библиотеки + Java + Node.js + screen)" fi # ── 4. раскладка каталогов ─────────────────────────────────────────────────── step "Создаю раскладку каталогов…" groupadd -f gameservers mkdir -p /home/cp/backups /home/cp/gameservers/files chown -R root:root /home chmod 755 /home /home/cp /home/cp/gameservers /home/cp/gameservers/files chmod 700 /home/cp/backups ok "/home/cp/gameservers/files — сюда кладутся сборки игр, /home/gs — сами сервера" # ── 5. база для игровых серверов ───────────────────────────────────────────── step "Настраиваю MariaDB для баз игровых серверов…" mkdir -p /run/mysqld && chown mysql:mysql /run/mysqld if ! mysqladmin ping >/dev/null 2>&1; then service mariadb start >/dev/null 2>&1 || /etc/init.d/mariadb start >/dev/null 2>&1 || true for _ in $(seq 1 45); do mysqladmin ping >/dev/null 2>&1 && break; sleep 2; done fi mysqladmin ping >/dev/null 2>&1 || die "MariaDB не поднялась" SQLCNF=/etc/mysql/mariadb.conf.d/50-server.cnf if [[ -f "$SQLCNF" ]]; then sed -i 's/^bind-address.*/bind-address = 0.0.0.0/' "$SQLCNF" grep -q '^max_connections' "$SQLCNF" || sed -i '/^\[mysqld\]/a max_connections = 1000' "$SQLCNF" fi DBADMPASS="$(pwgen -cns -1 20)" mysql -e "CREATE USER IF NOT EXISTS 'gsadmin'@'%' IDENTIFIED BY '$DBADMPASS'; ALTER USER 'gsadmin'@'%' IDENTIFIED BY '$DBADMPASS'; GRANT ALL PRIVILEGES ON *.* TO 'gsadmin'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;" service mariadb restart >/dev/null 2>&1 || /etc/init.d/mariadb restart >/dev/null 2>&1 || true sleep 3 umask 077; printf 'mysql_user=gsadmin\nmysql_pass=%s\n' "$DBADMPASS" > /root/.redl-node-credentials warn "MariaDB слушает 0.0.0.0:3306 — так требует панель. ОБЯЗАТЕЛЬНО ограничьте порт файрволом (см. конец вывода)" ok "Пользователь gsadmin создан, пароль в /root/.redl-node-credentials" # ── 6. SSH ─────────────────────────────────────────────────────────────────── step "Настраиваю SSH…" SSHD=/etc/ssh/sshd_config cp -n "$SSHD" "$SSHD.bak-redl" 2>/dev/null || true grep -q '^DenyGroups gameservers' "$SSHD" || echo 'DenyGroups gameservers' >> "$SSHD" if [[ "$SSHROOT" == "yes" ]]; then sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' "$SSHD" sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' "$SSHD" ROOTPASS="$(pwgen -cns -1 24)" echo "root:$ROOTPASS" | chpasswd printf 'ssh_user=root\nssh_pass=%s\n' "$ROOTPASS" >> /root/.redl-node-credentials ok "Вход root по паролю разрешён, пароль сгенерирован (в /root/.redl-node-credentials)" warn "Панель хранит этот пароль в своей базе в открытом виде — таково её устройство" else ROOTPASS="" warn "Вход root по паролю не менялся. Панель умеет подключаться ТОЛЬКО по логину и паролю," warn "и её команды требуют прав root — иначе создание серверов не заработает" fi if ! sshd -t 2>/dev/null; then cp "$SSHD.bak-redl" "$SSHD" die "Конфиг sshd не прошёл проверку — откатил, ничего не сломано" fi service ssh restart >/dev/null 2>&1 || service sshd restart >/dev/null 2>&1 || /etc/init.d/ssh restart >/dev/null 2>&1 || true ok "Группе gameservers вход по SSH запрещён (игровые пользователи не зайдут в шелл)" # ── 7. FTP ─────────────────────────────────────────────────────────────────── step "Настраиваю ProFTPD (доступ клиента к файлам сервера)…" PFTP=/etc/proftpd/proftpd.conf if [[ -f "$PFTP" ]]; then grep -q '^DefaultRoot ~' "$PFTP" || echo 'DefaultRoot ~' >> "$PFTP" grep -q '^RequireValidShell off' "$PFTP" || echo 'RequireValidShell off' >> "$PFTP" service proftpd restart >/dev/null 2>&1 || /etc/init.d/proftpd restart >/dev/null 2>&1 || true ok "Клиент видит только свой каталог (DefaultRoot ~)" else warn "Конфиг ProFTPD не найден — FTP не настроен, файловый менеджер панели работать не будет" fi # ── 8. SteamCMD ────────────────────────────────────────────────────────────── step "Ставлю SteamCMD (нужен для CS 1.6 / CS:S и других игр Steam)…" if [[ -x /root/steamcmd/steamcmd.sh ]]; then ok "SteamCMD уже установлен" else mkdir -p /root/steamcmd && cd /root/steamcmd if curl -fsSL -o steamcmd_linux.tar.gz https://media.steampowered.com/client/steamcmd_linux.tar.gz; then tar xzf steamcmd_linux.tar.gz && rm -f steamcmd_linux.tar.gz ok "SteamCMD в /root/steamcmd" else warn "Не удалось скачать SteamCMD — поставьте позже вручную" fi cd "$SRC" fi # ── 9. сторож docker ───────────────────────────────────────────────────────── step "Ставлю сторож docker…" cat > /usr/local/bin/gamenode-guard <<'GUARD' #!/bin/sh # Поднимает docker и MariaDB, если упали или машина перезагрузилась. docker info >/dev/null 2>&1 || service docker start >/dev/null 2>&1 mysqladmin ping >/dev/null 2>&1 || service mariadb start >/dev/null 2>&1 # запускаем контейнеры серверов, помеченных как работающие for c in $(docker ps -a --filter "name=^gs" --filter "status=exited" --format '{{.Names}}' 2>/dev/null); do docker start "$c" >/dev/null 2>&1 done GUARD chmod +x /usr/local/bin/gamenode-guard cat > /etc/cron.d/gamenode-guard <<'G' SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin */2 * * * * root /usr/local/bin/gamenode-guard >/dev/null 2>&1 @reboot root sleep 30; /usr/local/bin/gamenode-guard >/dev/null 2>&1 G chmod 644 /etc/cron.d/gamenode-guard service cron start >/dev/null 2>&1 || /etc/init.d/cron start >/dev/null 2>&1 || true ok "Проверка раз в 2 минуты + подъём после перезагрузки" # ── 10. проверка ───────────────────────────────────────────────────────────── step "Финальная проверка…" docker image inspect debian:stretch >/dev/null 2>&1 && ok "образ debian:stretch на месте" || warn "образа debian:stretch НЕТ" [[ -d /home/cp/gameservers/files ]] && ok "каталоги на месте" getent group gameservers >/dev/null && ok "группа gameservers создана" mysqladmin ping >/dev/null 2>&1 && ok "MariaDB отвечает" # проверяем, что контейнер реально создаётся и стартует if docker run --rm debian:stretch /bin/echo test >/dev/null 2>&1; then ok "Тестовый контейнер запускается — нода готова принимать игровые сервера" else warn "Тестовый контейнер НЕ запустился — проверьте docker вручную" fi CPU="$(nproc)"; RAM="$(free -m | awk '/^Mem:/{print $2}')"; HDD="$(df -BG --output=size / | tail -1 | tr -dc '0-9')" printf '\n%s=== Нода установлена ===%s\n\n' "$C_OK" "$C_0" printf '%sПодключение локации в панели (Админка → Локации → Добавить):%s\n' "$C_INF" "$C_0" printf ' IP : %s\n' "$NODEIP" printf ' Пользователь SSH: root\n' if [[ -n "$ROOTPASS" ]]; then printf ' Пароль SSH : %s\n' "$ROOTPASS" else printf ' Пароль SSH : (вы отказались менять — укажите действующий пароль root)\n' fi printf ' Ресурсы : %s ядер, %s МБ ОЗУ, %s ГБ диска\n\n' "$CPU" "$RAM" "$HDD" printf ' Данные MySQL для баз игровых серверов — в /root/.redl-node-credentials\n\n' printf '%sОБЯЗАТЕЛЬНО закройте порты файрволом (замените IP_ПАНЕЛИ на адрес панели):%s\n' "$C_W" "$C_0" printf ' ufw allow from IP_ПАНЕЛИ to any port 22 proto tcp\n' printf ' ufw allow from IP_ПАНЕЛИ to any port 3306 proto tcp\n' printf ' ufw deny 3306\n' printf ' # порты игровых серверов (например 7777, 22005, 25565) оставьте открытыми\n\n' printf '%sСборки игр%s кладите в /home/cp/gameservers/files/<код_игры>/\n' "$C_INF" "$C_0" printf ' Коды игр берите в Админка → Игры. Оригинальные ссылки на сборки в\n' printf ' установщике вели на сторонние сайты по HTTP — мы их не используем,\n' printf ' подробности в БЕЗОПАСНОСТЬ.md\n\n' printf 'Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом\n\n'