**Русский** · [English](CHANGES.en.md)

# HostinPL 5.6 · Что изменено по сравнению с оригиналом

Оригинал: **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`)
обёрнуты в `<?php if(@$captcha_enable == '1'): ?>`. Скрипт 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ги»).

### Полнота перевода

В словаре **1304 фразы** — это весь текст, который панель показывает человеку: кабинет, заказ
сервера, управление сервером (консоль с описанием всех RCON-команд, FTP, базы MySQL, файрвол,
планировщик задач, автоустановка модов), тикеты, FAQ целиком, вся админка, письма и служебные
сообщения планировщика.

Покрытие считается не на глаз: скрипт повторяет логику замены прямо на исходниках и показывает
строки, которые останутся русскими. По нему непокрытых мест **четыре**, и все четыре такими и
должны быть:

* «Русский» — подпись самого переключателя языка;
* `rus => Pyccĸий` и `ukr => Українська` в FAQ — это коды языков игрового сервера, а не интерфейс;
* регулярное выражение проверки имени `/^([А-ЯЁ])([а-яё]{1,15})$/u` — код, а не текст.

Отдельно поправлены два подводных камня, которые давали мешанину языков:

* предлог «в» был отдельной записью словаря и подменялся внутри ещё не переведённых предложений —
  получалось «at поле имя пользователя». Такие служебные слова переводятся только вместе с фразой;
* формат даты `d.m.Y в H:i` содержит русское «в» между датой и временем. Теперь он проходит через
  `t()` (31 вызов `date()` в 20 файлах), и в английском режиме получается `30.07.2026 14:22`.

Проверено живым обходом 45 страниц в английском режиме, включая формы создания и скрытые вкладки
настроек. Подробности — в [ИНСТРУКЦИЯ.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) — Хостинг с искусственным интеллектом.**
