#!/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<id> — сами сервера"

# ── 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'
