- панель работает на PHP 8.4 (оригинал под PHP 7.0): elFinder utf8_encode, apache_get_modules, warnings - капча выключена и управляется из админки (флаг captcha_enable + блок настроек) - закрыты две SQL-инъекции без авторизации (createAuthLog: пароль из POST и CF-Connecting-IP) - убрано хранение паролей в открытом виде в authlog, XSS в поле ref - установщики install-panel.sh и install-node.sh (оригинальный затирал sources.list репозиториями Debian 9) - современный Dockerfile (оригинальный на debian:stretch больше не собирается) - планировщик и автозапуск работают без systemd - брендинг REDL.IO, год 2026, ссылки на redl.io - документация на русском: ИЗМЕНЕНИЯ, БЕЗОПАСНОСТЬ, ИНСТРУКЦИЯ
257 lines
16 KiB
Bash
Executable File
257 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# Установка панели игрового хостинга в один клик.
|
||
# Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом.
|
||
#
|
||
# Что делает скрипт:
|
||
# 1. Ставит nginx, PHP 8.x (FPM), MariaDB
|
||
# 2. Разворачивает код панели в /var/www/hostinpl
|
||
# 3. Создаёт базу, пользователя и загружает схему
|
||
# 4. Прописывает конфиг (пароль БД, адрес, токен планировщика)
|
||
# 5. Настраивает планировщик (9 заданий) и сторож автозапуска
|
||
# 6. Создаёт администратора панели
|
||
#
|
||
# Запуск: sudo bash install-panel.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)"
|
||
[[ -d "$SRC/panel" ]] || die "Рядом со скриптом нет каталога panel/ — распакуйте репозиторий целиком"
|
||
|
||
WEBROOT=/var/www/hostinpl
|
||
DBNAME=hostin
|
||
DBUSER=hostinpl
|
||
PORT="${PORT:-80}"
|
||
|
||
printf '\n%s=== Панель игрового хостинга · установка ===%s\n' "$C_INF" "$C_0"
|
||
printf 'Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом\n\n'
|
||
|
||
# ── 0. вопросы ───────────────────────────────────────────────────────────────
|
||
read -rp "Домен или IP панели (например panel.example.com): " DOMAIN
|
||
[[ -n "$DOMAIN" ]] || die "Адрес не может быть пустым"
|
||
read -rp "E-Mail администратора (он же логин в панель): " ADMMAIL
|
||
[[ "$ADMMAIL" == *@*.* ]] || die "Похоже, это не e-mail"
|
||
read -rsp "Пароль администратора (минимум 6 символов): " ADMPASS; echo
|
||
[[ ${#ADMPASS} -ge 6 ]] || die "Пароль короче 6 символов"
|
||
|
||
if [[ -e "$WEBROOT" ]]; then
|
||
read -rp "Каталог $WEBROOT уже существует. Перезаписать код панели? (yes/no): " OW
|
||
[[ "$OW" == "yes" ]] || die "Отменено пользователем"
|
||
fi
|
||
|
||
. /etc/os-release 2>/dev/null || true
|
||
step "Система: ${PRETTY_NAME:-неизвестна}"
|
||
|
||
# ── 1. пакеты ────────────────────────────────────────────────────────────────
|
||
step "Устанавливаю пакеты (nginx, PHP-FPM, MariaDB)…"
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get update -qq
|
||
apt-get install -y -qq nginx mariadb-server unzip curl cron pwgen \
|
||
php-fpm php-mysql php-mbstring php-curl php-gd php-xml php-zip php-ssh2 >/dev/null
|
||
PHPV="$(ls -1 /etc/php/ 2>/dev/null | sort -V | tail -1)"
|
||
[[ -n "$PHPV" ]] || die "PHP не установился"
|
||
ok "PHP $PHPV, $(nginx -v 2>&1 | sed 's/.*nginx\///')"
|
||
|
||
# ── 2. службы (в контейнерах systemd может отсутствовать) ────────────────────
|
||
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 не поднялась — проверьте /var/log/mysql/"
|
||
ok "MariaDB отвечает"
|
||
|
||
# ── 3. база ──────────────────────────────────────────────────────────────────
|
||
step "Создаю базу данных…"
|
||
DBPASS="$(pwgen -cns -1 20)"
|
||
mysql -e "CREATE DATABASE IF NOT EXISTS \`$DBNAME\` DEFAULT CHARSET utf8;"
|
||
mysql -e "CREATE USER IF NOT EXISTS '$DBUSER'@'localhost' IDENTIFIED BY '$DBPASS';
|
||
ALTER USER '$DBUSER'@'localhost' IDENTIFIED BY '$DBPASS';
|
||
GRANT ALL PRIVILEGES ON \`$DBNAME\`.* TO '$DBUSER'@'localhost';
|
||
FLUSH PRIVILEGES;"
|
||
ok "База $DBNAME, пользователь $DBUSER (права только на свою базу)"
|
||
|
||
# ── 4. файлы ─────────────────────────────────────────────────────────────────
|
||
step "Копирую код панели в $WEBROOT…"
|
||
mkdir -p "$WEBROOT"
|
||
cp -a "$SRC/panel/." "$WEBROOT/"
|
||
if [[ -f "$WEBROOT/hostinpl.sql" ]]; then
|
||
TABLES="$(mysql -N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DBNAME'")"
|
||
if [[ "$TABLES" -lt 5 ]]; then
|
||
mysql "$DBNAME" < "$WEBROOT/hostinpl.sql"
|
||
ok "Схема загружена: $(mysql -N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DBNAME'") таблиц"
|
||
else
|
||
warn "В базе уже $TABLES таблиц — схему не перезаписываю"
|
||
fi
|
||
rm -f "$WEBROOT/hostinpl.sql"
|
||
fi
|
||
|
||
# ── 5. конфиг ────────────────────────────────────────────────────────────────
|
||
step "Настраиваю конфигурацию…"
|
||
CRONTOKEN="$(pwgen -cns -1 24)"
|
||
CFG="$WEBROOT/application/config.php"
|
||
PROTO=http; [[ "$PORT" == "443" ]] && PROTO=https
|
||
URL="$PROTO://$DOMAIN/"; [[ "$PORT" != "80" && "$PORT" != "443" ]] && URL="http://$DOMAIN:$PORT/"
|
||
# Значения подставляются через callback, а не строкой замены: иначе символы вида $1
|
||
# в сгенерированном пароле PHP принял бы за обратную ссылку и записал бы мусор.
|
||
php -r '
|
||
$f=$argv[1];$s=file_get_contents($f);
|
||
$set=function($s,$key,$val,$anchor=false){
|
||
$re="~".($anchor?"^":"")."\x27".preg_quote($key,"~")."\x27(\s*)=>(\s*)\x27[^\x27]*\x27~u".($anchor?"m":"");
|
||
return preg_replace_callback($re,function($m)use($key,$val){
|
||
return "\x27".$key."\x27".$m[1]."=>".$m[2]."\x27".$val."\x27";
|
||
},$s,1);
|
||
};
|
||
$s=$set($s,"db_username",$argv[2]);
|
||
$s=$set($s,"db_password",$argv[3]);
|
||
$s=$set($s,"url",$argv[4]);
|
||
$s=$set($s,"token",$argv[5],true);
|
||
file_put_contents($f,$s);' "$CFG" "$DBUSER" "$DBPASS" "$URL" "$CRONTOKEN"
|
||
php -l "$CFG" >/dev/null || die "Конфиг повреждён — установка прервана"
|
||
grep -q "'db_password' => *'$DBPASS'" "$CFG" || grep -q "$DBPASS" "$CFG" || die "Пароль БД не попал в конфиг"
|
||
umask 077; printf 'db_user=%s\ndb_pass=%s\ncron_token=%s\n' "$DBUSER" "$DBPASS" "$CRONTOKEN" > /root/.redl-panel-credentials
|
||
ok "Конфиг записан, креды сохранены в /root/.redl-panel-credentials"
|
||
|
||
# ── 6. права ─────────────────────────────────────────────────────────────────
|
||
step "Выставляю права…"
|
||
chown -R www-data:www-data "$WEBROOT"
|
||
find "$WEBROOT" -type d -exec chmod 750 {} +
|
||
find "$WEBROOT" -type f -exec chmod 640 {} +
|
||
chmod -R 770 "$WEBROOT/tmp"
|
||
ok "Владелец www-data, конфиг недоступен извне"
|
||
|
||
# ── 7. PHP ───────────────────────────────────────────────────────────────────
|
||
step "Настраиваю PHP $PHPV…"
|
||
INI="/etc/php/$PHPV/fpm/php.ini"
|
||
sed -i 's/^short_open_tag = Off/short_open_tag = On/' "$INI"
|
||
sed -i 's/^upload_max_filesize = .*/upload_max_filesize = 90M/' "$INI"
|
||
sed -i 's/^post_max_size = .*/post_max_size = 360M/' "$INI"
|
||
sed -i 's|^;\?date.timezone =.*|date.timezone = Europe/Moscow|' "$INI"
|
||
mkdir -p /run/php
|
||
service "php$PHPV-fpm" restart >/dev/null 2>&1 || /etc/init.d/"php$PHPV-fpm" restart >/dev/null 2>&1 || true
|
||
sleep 2
|
||
[[ -S "/run/php/php$PHPV-fpm.sock" ]] || die "PHP-FPM сокет не создан"
|
||
ok "short_open_tag включён (панель использует <? ?>), загрузка до 90 МБ"
|
||
|
||
# ── 8. nginx ─────────────────────────────────────────────────────────────────
|
||
step "Настраиваю nginx (порт $PORT)…"
|
||
cat > /etc/nginx/sites-available/hostinpl <<NGINX
|
||
server {
|
||
listen 0.0.0.0:$PORT default_server;
|
||
server_name $DOMAIN _;
|
||
root $WEBROOT;
|
||
index index.php;
|
||
client_max_body_size 360M;
|
||
|
||
# редиректы без хоста и порта — иначе за обратным прокси уводит на :$PORT
|
||
absolute_redirect off;
|
||
port_in_redirect off;
|
||
|
||
location / { try_files \$uri \$uri/ /index.php\$is_args\$args; }
|
||
|
||
location ~ \.php\$ {
|
||
include snippets/fastcgi-php.conf;
|
||
fastcgi_pass unix:/run/php/php$PHPV-fpm.sock;
|
||
fastcgi_read_timeout 300;
|
||
}
|
||
|
||
# закрываем служебное
|
||
location ~ /\.(ht|git) { deny all; }
|
||
location ~* \.(sql|log)\$ { deny all; }
|
||
location ^~ /application/config.php { deny all; }
|
||
}
|
||
NGINX
|
||
ln -sfn /etc/nginx/sites-available/hostinpl /etc/nginx/sites-enabled/hostinpl
|
||
rm -f /etc/nginx/sites-enabled/default
|
||
nginx -t >/dev/null 2>&1 || { nginx -t; die "Конфиг nginx не прошёл проверку"; }
|
||
service nginx restart >/dev/null 2>&1 || /etc/init.d/nginx restart >/dev/null 2>&1 || nginx
|
||
sleep 1
|
||
ok "Сайт слушает 0.0.0.0:$PORT"
|
||
|
||
# ── 9. планировщик ───────────────────────────────────────────────────────────
|
||
step "Настраиваю планировщик…"
|
||
B="http://127.0.0.1:$PORT/main/cron"
|
||
{
|
||
echo 'SHELL=/bin/sh'
|
||
echo 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin'
|
||
echo "0 0 * * * root curl -s -m 300 '$B/index?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
echo "* * * * * root curl -s -m 55 '$B/gameServers?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
echo "* * * * * root curl -s -m 55 '$B/tasks?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
echo "*/10 * * * * root curl -s -m 300 '$B/serverReloader?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
echo "*/30 * * * * root curl -s -m 300 '$B/stopServers?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
echo "*/30 * * * * root curl -s -m 300 '$B/stopServersQuery?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
echo "0 * * * * root curl -s -m 300 '$B/updateStats?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
echo "0 * * * * root curl -s -m 300 '$B/updateStatsLocations?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
echo "0 3 * * 0 root curl -s -m 300 '$B/clearLogs?token=$CRONTOKEN' >/dev/null 2>&1"
|
||
} > /etc/cron.d/hostinpl
|
||
chmod 644 /etc/cron.d/hostinpl
|
||
service cron start >/dev/null 2>&1 || /etc/init.d/cron start >/dev/null 2>&1 || true
|
||
ok "9 заданий панели (статистика, задачи, автоотключение просрочки)"
|
||
|
||
# ── 10. сторож автозапуска (нужен там, где нет systemd) ──────────────────────
|
||
step "Ставлю сторож автозапуска…"
|
||
cat > /usr/local/bin/hostinpl-guard <<GUARD
|
||
#!/bin/sh
|
||
# Поднимает стек панели, если что-то упало или машина перезагрузилась.
|
||
pgrep -x mariadbd >/dev/null 2>&1 || pgrep -x mysqld >/dev/null 2>&1 || {
|
||
mkdir -p /run/mysqld; chown mysql:mysql /run/mysqld
|
||
service mariadb start >/dev/null 2>&1 || /etc/init.d/mariadb start >/dev/null 2>&1
|
||
}
|
||
pgrep -f "^php-fpm$PHPV: master" >/dev/null 2>&1 || service php$PHPV-fpm start >/dev/null 2>&1
|
||
curl -sf -o /dev/null http://127.0.0.1:$PORT/account/login || service nginx start >/dev/null 2>&1
|
||
pgrep -x cron >/dev/null 2>&1 || service cron start >/dev/null 2>&1
|
||
GUARD
|
||
chmod +x /usr/local/bin/hostinpl-guard
|
||
cat > /etc/cron.d/hostinpl-guard <<'G'
|
||
SHELL=/bin/sh
|
||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||
* * * * * root /usr/local/bin/hostinpl-guard >/dev/null 2>&1
|
||
@reboot root sleep 20; /usr/local/bin/hostinpl-guard >/dev/null 2>&1
|
||
G
|
||
chmod 644 /etc/cron.d/hostinpl-guard
|
||
ok "Проверка раз в минуту и подъём после перезагрузки"
|
||
|
||
# ── 11. администратор ────────────────────────────────────────────────────────
|
||
step "Создаю администратора панели…"
|
||
HASH="$(php -r 'echo md5($argv[1]);' "$ADMPASS")"
|
||
mysql "$DBNAME" <<SQL
|
||
DELETE FROM users WHERE user_email='$ADMMAIL';
|
||
INSERT INTO users
|
||
(user_email,user_password,user_firstname,user_lastname,user_status,user_balance,
|
||
user_access_level,user_date_reg,user_online_date,user_promo_date,user_activate,
|
||
key_activate,ref,rmoney,bonuses,test_server,user_last_date)
|
||
VALUES
|
||
('$ADMMAIL','$HASH','Администратор','Панели',1,0.00,3,NOW(),UNIX_TIMESTAMP(),
|
||
CURDATE(),1,'',0,0.00,0.00,'2',NOW());
|
||
SQL
|
||
ok "Логин $ADMMAIL, уровень доступа 3 (полный)"
|
||
|
||
# ── 12. проверка ─────────────────────────────────────────────────────────────
|
||
step "Проверяю работу панели…"
|
||
sleep 2
|
||
CODE="$(curl -s -o /tmp/redl-check.html -w '%{http_code}' -L "http://127.0.0.1:$PORT/account/login" || true)"
|
||
[[ "$CODE" == "200" ]] || die "Страница входа вернула $CODE (ожидался 200). Смотрите /var/log/nginx/error.log"
|
||
grep -q 'kt_login_signin_form' /tmp/redl-check.html || die "Страница входа отдалась без формы"
|
||
rm -f /tmp/redl-check.html
|
||
ok "Страница входа отвечает, форма на месте"
|
||
|
||
printf '\n%s=== Установка завершена ===%s\n\n' "$C_OK" "$C_0"
|
||
printf ' Адрес панели : %s\n' "$URL"
|
||
printf ' Логин : %s\n' "$ADMMAIL"
|
||
printf ' Пароль : тот, что вы ввели при установке\n'
|
||
printf ' Креды БД : /root/.redl-panel-credentials\n\n'
|
||
printf '%sДальше:%s\n' "$C_INF" "$C_0"
|
||
printf ' 1. Капча выключена. Включить: Админка → Настройки → Прочие настройки → «Защита от ботов»\n'
|
||
printf ' (сначала впишите оба ключа с google.com/recaptcha, потом переключайте)\n'
|
||
printf ' 2. Платёжные системы: Админка → Настройки → Платёжные системы\n'
|
||
printf ' 3. Игровую ноду ставьте скриптом install-node.sh и подключайте в Админка → Локации\n\n'
|
||
printf 'Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом\n\n'
|