**Русский** · [English](CHANGES.en.md) # Что изменено по сравнению с оригиналом Оригинал: **HostinPL 5.6** в виде «nulled»-сборки (форк `Xopowblu-4EJlOBEK/HostinPL-5.6`), написанной под Debian 9 и PHP 7.0. Ниже — всё, что мы поменяли, с указанием файлов и строк. Правки в коде панели лежат в `panel/`. **Поддерживается и развивается с помощью [REDL.IO](https://redl.io) — Хостинг с искусственным интеллектом.** --- ## 1. Панель запущена на современном PHP Оригинал написан под PHP 7.0. На PHP 8 часть кода падала насмерть. Сейчас панель работает на **PHP 8.4**: обход всех 21 раздела (главная, сервера, новости, статус, тикеты, веб-хостинг и вся админка) даёт **200 на каждом и ноль ошибок в логе**. | Файл | Было | Стало | |------|------|-------| | `engine/engine_ftp/elFinder.class.php:4497` | `utf8_encode()` — функция удалена в PHP 8.2, файловый менеджер падал с фатальной ошибкой | Обёрнута в `function_exists()`, фолбэк `mb_convert_encoding($str,'UTF-8','ISO-8859-1')` | | `application/views/admin/checksys/index.php:22` | `apache_get_modules()` — существует только в Apache с mod_php, под nginx раздел «Проверка системы» отдавал 500 | Обёрнута в `function_exists()`, иначе проверка по `REQUEST_URI` | | `application/views/admin/index.php:215` | `$item['invoice_ammount']` использовался после `foreach`, при пустом списке счетов — Undefined variable | Проверка `isset()` | | `application/models/users.php:199-206` | `$city[1]`, `$country[1]`, `$countryCode[1]` без проверки совпадения регулярки | Проверки `isset()` | | `application/controllers/common/loginheader.php:35` | `$_GET['ref']` без `isset` | `isset()` + приведение к `(int)` | ### Что оказалось ложной тревогой Библиотека **phpseclib 1.x** в составе панели использует `create_function()`, удалённую в PHP 8. Выглядело как блокирующая проблема, но проверка показала: **phpseclib не подключается ни одним `include` во всей панели** — это мёртвый код. Связь с игровыми нодами идёт через нативное расширение `php-ssh2` (`engine/libs/ssh2.php` → `ssh2_exec`). Ничего чинить не потребовалось. Так же безопасен `get_magic_quotes_gpc()` в `elFinderConnector.class.php:320` — он закрыт условием `version_compare(PHP_VERSION,'5.4','<') && ...`, и на PHP 8 до вызова дело не доходит. --- ## 2. Капча выключена и управляется из админки Раньше капча была вшита жёстко: без валидных ключей Google **войти в панель было невозможно**, на форме висело «ERROR for site owner: Invalid site key». Теперь появился флаг `captcha_enable` в `application/config.php` (по умолчанию `0` — выключена). **Бэкенд.** Во все четыре валидатора добавлена строка `if($this->config->captcha_enable != '1') return $result;` перед проверкой капчи: * `application/controllers/account/login.php` — вход * `application/controllers/common/loginheader.php` — регистрация и форма обратной связи (2 места) * `application/controllers/tickets/create.php` — создание тикета **Вёрстка.** Пять виджетов капчи (4 в `views/common/loginheader.php`, 1 в `views/tickets/create.php`) обёрнуты в ``. Скрипт Google `api.js` подключается только при включённой капче. Все вызовы `grecaptcha.reset(...)` заменены на `window.grecaptcha && grecaptcha.reset(...)` — иначе при выключенной капче JavaScript падал в обработчиках ошибок и формы переставали отвечать. **Админка.** В `views/admin/settings.php`, вкладка «Прочие настройки», добавлен блок «Защита от ботов (reCAPTCHA v2)»: переключатель Выключена/Включена и поля Site key и Secret key. Проверено в обе стороны: при выключенной капче вход и регистрация проходят и пользователь реально создаётся в базе; при включённой — свежая сессия получает «Подтвердите, что вы не робот!» и виджет возвращается на страницу. > **Осторожно при добавлении своих настроек.** Механизм сохранения настроек ищет строки конфига > **по подстроке** (`strpos`). Поэтому флаг назван `captcha_enable`, а не `captcha`: строка > `captcha` содержится в `recaptcha` и `secret_recaptcha`, и сохранение перезаписало бы не тот > параметр. Новые ключи не должны быть подстрокой существующих. --- ## 3. Закрыты уязвимости Кратко (подробно с кодом — в [БЕЗОПАСНОСТЬ.md](БЕЗОПАСНОСТЬ.md)): * **Две SQL-инъекции, доступные без авторизации** в `application/models/users.php` (`createAuthLog()`): в журнал входов без экранирования попадали пароль из формы и IP из заголовка `CF-Connecting-IP`, который вообще не проверялся. Все значения теперь проходят через `$this->db->escape()` / `(int)`, заголовок проверяется `filter_var(..., FILTER_VALIDATE_IP)`. * **Пароли в открытом виде**: `login.php` писал реальный пароль в таблицу `authlog` при каждой попытке входа, включая неудачные. Убрано. * **XSS** в скрытом поле `ref` формы регистрации — добавлен `htmlspecialchars()`. * Запрос к геосервису `ip-api.com` выполняется только для валидного IP и с `urlencode()`. --- ## 4. Установщик переписан с нуля Оригинальный `install` был опасен на любой современной системе: * `echo "deb ... stretch main" > /etc/apt/sources.list` — **затирал список репозиториев** и заменял его на Debian 9, после чего apt ломался * ставил `php7.0` и жёстко правил `/etc/php/7.0/apache2/php.ini` * переводил MariaDB на `bind-address = 0.0.0.0` без единого предупреждения * работал только при `/etc/issue.net` == `Debian9`, иначе просто отказывался запускаться * пароли и токены генерировал, но нигде не сохранял — их можно было только записать с экрана * при любой ошибке продолжал работу: все команды заканчивались на `> /dev/null 2>&1` Новые скрипты: **`install-panel.sh`** — nginx, PHP 8.x (версия определяется автоматически), MariaDB, база со случайным паролем, конфиг, планировщик, сторож автозапуска, создание администратора и финальная проверка, что страница входа действительно отдаётся с формой. Идемпотентен: повторный запуск не затирает уже загруженную базу. Креды пишутся в `/root/.redl-panel-credentials` (chmod 600). `set -euo pipefail` — при ошибке скрипт останавливается, а не делает вид, что всё хорошо. **`install-node.sh`** — Docker из официального репозитория, сборка образа, каталоги `/home/cp/gameservers/files`, группа `gameservers`, MariaDB для баз игровых серверов, SteamCMD, ProFTPD, настройка sshd с откатом конфига, если `sshd -t` не проходит. **Перед началом проверяет, может ли Docker вообще работать на этой машине** (`unshare -Ur`), и честно предупреждает, если нет. В конце печатает готовые данные для подключения локации и команды файрвола. Ни один из скриптов не трогает `/etc/apt/sources.list`. --- ## 5. Образ игровых серверов пересобран Оригинальный `docker/Dockerfile.original-stretch` собран на `debian:stretch`, а репозитории Debian 9 отключены с 2023 года — **образ больше не собирается**, `apt-get update` внутри падает. Новый `docker/Dockerfile` — на Debian 12 (bookworm), но **обязательно с тегом `debian:stretch`**: это имя жёстко прописано в коде панели (`application/models/servers.php:642`, `docker create ... debian:stretch`), менять его без правки панели нельзя. Что внутри: 32-битные библиотеки (SA-MP, CRMP, MTA, старые CS собраны под i386), `screen` для консолей, Java для Minecraft, Node.js 20 для RAGE:MP, `gdb` для разбора крашей. Node.js берётся из NodeSource **по HTTPS с проверкой ключа**, а не как раньше. --- ## 6. Работа без systemd Панель штатно рассчитана на systemd. В контейнерных VPS его нет, поэтому добавлены сторожа на cron: `/usr/local/bin/hostinpl-guard` (панель) и `/usr/local/bin/gamenode-guard` (нода). Раз в минуту (нода — раз в 2 минуты) они проверяют MariaDB, PHP-FPM, nginx, Docker и cron и поднимают то, что упало; плюс задание `@reboot` для подъёма после перезагрузки. Живость панели определяется **HTTP-запросом** к странице входа, а не поиском процесса по имени — поиск по шаблону ловил бы собственную командную строку сторожа. --- ## 7. Планировщик Оригинал прописывал 9 заданий на публичный домен панели. Теперь они ходят на `127.0.0.1` (не зависит от DNS и внешней доступности) и получают таймауты `-m`, чтобы зависший запрос не копился в процессах. > **Грабля с токеном.** Токен планировщика нельзя выцеплять из конфига простым `grep "'token'"` — > под это же условие попадает строка `'yk_password1' => 'token'`, и в URL уезжают два значения > через перевод строки, после чего задания молча не работают. В установщике токен пишется > напрямую при генерации конфига. --- ## 8. Языки интерфейса: русский и английский Оригинал был только на русском, весь текст вшит в шаблоны. В сборке появился слой перевода: * `engine/main/lang.php` — класс `Lang` и глобальный помощник `t()` * `application/lang/ru.php`, `application/lang/en.php` — словари * Порядок выбора языка: параметр `?lang=` → cookie `lang` → **заголовок браузера `Accept-Language`** → значение по умолчанию из конфига * Переключатель (RU / EN) в шапке кабинета, админки и в подвале страницы входа * Фразы, которых нет в словаре, остаются на русском — из интерфейса ничего не может пропасть Перевод применяется к готовому ответу через `ob_start()`, поэтому 200 файлов шаблонов переписывать не понадобилось, и покрытие сразу распространяется на админку и письма. Замена работает только на границах слов: без этого короткий ключ портил длинные слова («Мод» превращал «Модуль» в «Modуль», «день» делал из «деньги» «dayги»). Полнота проверена обходом 20 разделов: непереведённых фраз не осталось, мешанины языков нет. Подробности — в [ИНСТРУКЦИЯ.md](ИНСТРУКЦИЯ.md), раздел «Язык интерфейса». --- ## 9. Брендинг и даты * Год в подвалах: `2020©` → `2026©` (`views/common/footer.php`, `views/common/loginheader.php`) * Название и описание — REDL.IO: `application/config.php` (`description`, `keywords`, `mail_sender`, `mail_from`), подвалы, раздел «Проверка системы» * Ссылки на сторонние сайты прежнего владельца сборки (`hostinpl.ru`, `osmp.ga`) заменены на `redl.io` * Ссылки «Сообщество VK» на чужое сообщество заменены на `redl.io` (`footer.php`, `views/main/index.php`, `views/offline/index.php`) * Все 15 шаблонов писем: шапка «REDL.IO / Хостинг с искусственным интеллектом», подпись «С уважением, Администрация REDL.IO» **Авторские копирайт-заголовки в коде не удалялись** — они остались во всех файлах, где были изначально. --- ## 10. phpMyAdmin Панель ссылается на `/phpmyadmin` из админки. Оригинальный установщик ставил phpMyAdmin через Apache, что на nginx не работало. Теперь он ставится из репозитория дистрибутива и отдаётся самим nginx. > **Грабля за обратным прокси.** nginx на адрес `/phpmyadmin` (без слеша) отвечает редиректом > и по умолчанию подставляет в него **собственный порт**, например > `http://panel.example.com:8095/phpmyadmin/`. Если панель стоит за обратным прокси, такой адрес > снаружи недоступен и ссылка из админки не открывается. Лечится > `absolute_redirect off; port_in_redirect off;` — это уже прописано в конфиге, который создаёт > установщик. --- ## Чего мы НЕ делали * Не переписывали хеширование паролей. Панель хранит пароли как **MD5 без соли** (`md5($password)`, колонка `varchar(32)`). Переход на `password_hash()` затрагивает вход, регистрацию, восстановление, смену пароля и требует миграции существующих пользователей. * Не переводили создание игровых серверов с Docker на что-то другое. * Не удаляли `panel/application/public/js/proxy/proxy.php` — файл обезврежен, но оставлен как свидетельство (см. [БЕЗОПАСНОСТЬ.md](БЕЗОПАСНОСТЬ.md)). * Не тестировали платёжные шлюзы живыми платежами и VK-авторизацию. * Не проверяли работу самих игровых серверов: для этого нужна нода с Docker. --- **Поддерживается и развивается с помощью [REDL.IO](https://redl.io) — Хостинг с искусственным интеллектом.**