Панель: - engine/main/lang.php: класс Lang, автоопределение языка (?lang= -> cookie -> Accept-Language -> конфиг) - перевод применяется к готовому ответу через ob_start(): покрывает всю панель, админку и письма, не требуя правки 200 файлов шаблонов; отсутствующая фраза остаётся русской - замена только на границах слов, иначе короткий ключ портил длинные слова (Модуль -> Modуль) - AJAX-ответы переводятся отдельно (json_encode экранирует кириллицу в \uXXXX) - application/lang/en.php: 657 переводов; ru.php как точка расширения - переключатель RU/EN в шапке кабинета, админки и в подвале страницы входа - 'lang' в config.php — язык по умолчанию - проверено обходом 20 разделов: 0 непереведённых фраз, 0 мешанины языков, 0 фаталов Документация — теперь на русском и английском: - README.en.md, CHANGES.en.md, SECURITY.en.md, GUIDE.en.md - переключатели языка в начале каждого документа - раздел «Язык интерфейса» в инструкции: как работает, как добавить свой язык
221 lines
11 KiB
Markdown
221 lines
11 KiB
Markdown
[Русский](ИЗМЕНЕНИЯ.md) · **English**
|
|
|
|
# What changed compared to the original
|
|
|
|
Original: **HostinPL 5.6** as a "nulled" build (the `Xopowblu-4EJlOBEK/HostinPL-5.6` fork),
|
|
written for Debian 9 and PHP 7.0.
|
|
|
|
Below is everything we changed, with file names and line numbers. Panel code changes live in `panel/`.
|
|
|
|
**Maintained and developed with [REDL.IO](https://redl.io) — AI-powered hosting.**
|
|
|
|
---
|
|
|
|
## 1. The panel now runs on modern PHP
|
|
|
|
The original targets PHP 7.0. On PHP 8 parts of it died outright. It now runs on **PHP 8.4**:
|
|
walking all 21 sections (home, servers, news, status, tickets, web hosting and the whole admin area)
|
|
returns **200 on every one and zero errors in the log**.
|
|
|
|
| File | Before | After |
|
|
|------|--------|-------|
|
|
| `engine/engine_ftp/elFinder.class.php:4497` | `utf8_encode()` — removed in PHP 8.2, the file manager died with a fatal error | Wrapped in `function_exists()`, falls back to `mb_convert_encoding($str,'UTF-8','ISO-8859-1')` |
|
|
| `application/views/admin/checksys/index.php:22` | `apache_get_modules()` — only exists under Apache with mod_php, so the "System check" page returned 500 under nginx | Wrapped in `function_exists()`, otherwise checked via `REQUEST_URI` |
|
|
| `application/views/admin/index.php:215` | `$item['invoice_ammount']` used after the `foreach`, producing an Undefined variable when there were no invoices | `isset()` check |
|
|
| `application/models/users.php:199-206` | `$city[1]`, `$country[1]`, `$countryCode[1]` used without checking that the regex matched | `isset()` checks |
|
|
| `application/controllers/common/loginheader.php:35` | `$_GET['ref']` without `isset` | `isset()` plus a cast to `(int)` |
|
|
|
|
### What turned out to be a false alarm
|
|
|
|
The bundled **phpseclib 1.x** uses `create_function()`, which PHP 8 removed. It looked like a blocker,
|
|
but checking showed that **phpseclib is not included anywhere in the panel** — it is dead code.
|
|
Communication with game nodes goes through the native `php-ssh2` extension
|
|
(`engine/libs/ssh2.php` → `ssh2_exec`). Nothing needed fixing.
|
|
|
|
`get_magic_quotes_gpc()` in `elFinderConnector.class.php:320` is equally harmless: it sits behind
|
|
`version_compare(PHP_VERSION,'5.4','<') && ...`, so on PHP 8 it is never reached.
|
|
|
|
---
|
|
|
|
## 2. The captcha is off and managed from the admin area
|
|
|
|
The captcha used to be hard-wired: without valid Google keys **it was impossible to log in**,
|
|
and the form displayed "ERROR for site owner: Invalid site key". There is now a `captcha_enable`
|
|
flag in `application/config.php`, defaulting to `0` (off).
|
|
|
|
**Backend.** All four validators received the line
|
|
`if($this->config->captcha_enable != '1') return $result;` before the captcha check:
|
|
|
|
* `application/controllers/account/login.php` — login
|
|
* `application/controllers/common/loginheader.php` — registration and the contact form (2 places)
|
|
* `application/controllers/tickets/create.php` — ticket creation
|
|
|
|
**Markup.** The five captcha widgets (4 in `views/common/loginheader.php`, 1 in
|
|
`views/tickets/create.php`) are wrapped in `<?php if(@$captcha_enable == '1'): ?>`. Google's `api.js`
|
|
is only loaded when the captcha is on. Every `grecaptcha.reset(...)` call became
|
|
`window.grecaptcha && grecaptcha.reset(...)` — otherwise, with the captcha off, JavaScript threw
|
|
inside the error handlers and the forms stopped responding.
|
|
|
|
**Admin area.** `views/admin/settings.php`, the "Other settings" tab, gained a
|
|
"Bot protection (reCAPTCHA v2)" block: an Off/On selector plus Site key and Secret key fields.
|
|
|
|
Verified both ways: with the captcha off, login and registration succeed and the user is really
|
|
created in the database; with it on, a fresh session gets "Confirm that you are not a robot!"
|
|
and the widget returns to the page.
|
|
|
|
> **Careful when adding your own settings.** The settings writer matches configuration lines
|
|
> **by substring** (`strpos`). That is why the flag is called `captcha_enable` and not `captcha`:
|
|
> the string `captcha` occurs inside `recaptcha` and `secret_recaptcha`, and saving would have
|
|
> overwritten the wrong parameter. New keys must not be substrings of existing ones.
|
|
|
|
---
|
|
|
|
## 3. Vulnerabilities fixed
|
|
|
|
In brief (full detail with code in [SECURITY.en.md](SECURITY.en.md)):
|
|
|
|
* **Two pre-authentication SQL injections** in `application/models/users.php` (`createAuthLog()`):
|
|
the login journal received the password from the form and the IP from the `CF-Connecting-IP`
|
|
header — which was not validated at all — without escaping. Every value now goes through
|
|
`$this->db->escape()` or a cast to `(int)`, and the header is validated with
|
|
`filter_var(..., FILTER_VALIDATE_IP)`.
|
|
* **Plaintext passwords**: `login.php` wrote the real password into the `authlog` table on every
|
|
login attempt, including failed ones. Removed.
|
|
* **XSS** in the hidden `ref` field of the registration form — `htmlspecialchars()` added.
|
|
* The request to the `ip-api.com` geolocation service now only runs for a valid IP and uses `urlencode()`.
|
|
|
|
---
|
|
|
|
## 4. The installer was rewritten from scratch
|
|
|
|
The original `install` was dangerous on any current system:
|
|
|
|
* `echo "deb ... stretch main" > /etc/apt/sources.list` — **wiped the repository list** and replaced
|
|
it with Debian 9, after which apt was broken
|
|
* installed `php7.0` and hard-coded edits to `/etc/php/7.0/apache2/php.ini`
|
|
* switched MariaDB to `bind-address = 0.0.0.0` without a word of warning
|
|
* only ran when `/etc/issue.net` said `Debian9`, and otherwise refused to start
|
|
* generated passwords and tokens but saved them nowhere — you could only copy them off the screen
|
|
* carried on after any failure: every command ended in `> /dev/null 2>&1`
|
|
|
|
The new scripts:
|
|
|
|
**`install-panel.sh`** — nginx, PHP 8.x (version detected automatically), MariaDB, a database with a
|
|
random password, configuration, scheduler, autostart watchdog, administrator creation, and a final
|
|
check that the login page really is served with its form. Idempotent: running it again does not wipe
|
|
a database that is already loaded. Credentials are written to `/root/.redl-panel-credentials`
|
|
(chmod 600). With `set -euo pipefail` the script stops on error instead of pretending all is well.
|
|
|
|
**`install-node.sh`** — Docker from the official repository, image build, the
|
|
`/home/cp/gameservers/files` layout, the `gameservers` group, MariaDB for game server databases,
|
|
SteamCMD, ProFTPD, and sshd configuration that rolls back if `sshd -t` fails. **Before doing anything
|
|
it checks whether Docker can work on this machine at all** (`unshare -Ur`) and says so plainly if it
|
|
cannot. At the end it prints ready-to-use location details and firewall commands.
|
|
|
|
Neither script touches `/etc/apt/sources.list`.
|
|
|
|
---
|
|
|
|
## 5. The game server image was rebuilt
|
|
|
|
The original `docker/Dockerfile.original-stretch` is based on `debian:stretch`, and the Debian 9
|
|
repositories were shut down in 2023 — **that image no longer builds**, `apt-get update` inside it fails.
|
|
|
|
The new `docker/Dockerfile` is based on Debian 12 (bookworm) but **must still be tagged
|
|
`debian:stretch`**: that name is hard-coded in the panel
|
|
(`application/models/servers.php:642`, `docker create ... debian:stretch`) and cannot be changed
|
|
without editing the panel.
|
|
|
|
What is inside: 32-bit libraries (SA-MP, CRMP, MTA and older CS builds are i386), `screen` for
|
|
consoles, Java for Minecraft, Node.js 20 for RAGE:MP, and `gdb` for crash analysis.
|
|
Node.js comes from NodeSource **over HTTPS with repository key verification**, unlike before.
|
|
|
|
---
|
|
|
|
## 6. Working without systemd
|
|
|
|
The panel assumes systemd is present. Container VPSes do not have it, so watchdogs were added on
|
|
cron: `/usr/local/bin/hostinpl-guard` (panel) and `/usr/local/bin/gamenode-guard` (node).
|
|
Once a minute (every 2 minutes on a node) they check MariaDB, PHP-FPM, nginx, Docker and cron and
|
|
restart whatever died, plus an `@reboot` job to bring everything up after a restart.
|
|
|
|
Panel liveness is determined by an **HTTP request** to the login page rather than by searching for a
|
|
process name — a pattern search would have matched the watchdog's own command line.
|
|
|
|
---
|
|
|
|
## 7. Scheduler
|
|
|
|
The original pointed its 9 jobs at the panel's public domain. They now go to `127.0.0.1`
|
|
(independent of DNS and external reachability) and carry `-m` timeouts so that a hung request does
|
|
not pile up processes.
|
|
|
|
> **Token pitfall.** You cannot extract the scheduler token from the config with a plain
|
|
> `grep "'token'"` — the line `'yk_password1' => 'token'` matches the same pattern, two values end
|
|
> up in the URL separated by a newline, and the jobs then fail silently. The installer writes the
|
|
> token directly when it generates the configuration.
|
|
|
|
---
|
|
|
|
## 8. Interface languages: Russian and English
|
|
|
|
The original was Russian-only, with every string hard-coded in the templates. The build now has a
|
|
translation layer:
|
|
|
|
* `engine/main/lang.php` — the `Lang` class and the global `t()` helper
|
|
* `application/lang/ru.php`, `application/lang/en.php` — dictionaries
|
|
* Language selection order: `?lang=` parameter → `lang` cookie → **the browser's `Accept-Language`
|
|
header** → panel default
|
|
* A switcher (RU / EN) in the header of the client area and on the login page
|
|
* Anything not yet translated falls back to the original Russian text, so nothing can disappear
|
|
from the interface
|
|
|
|
Details and current coverage: see [GUIDE.en.md](GUIDE.en.md), the "Interface language" section.
|
|
|
|
---
|
|
|
|
## 9. Branding and dates
|
|
|
|
* The year in footers: `2020©` → `2026©` (`views/common/footer.php`, `views/common/loginheader.php`)
|
|
* Name and description are REDL.IO: `application/config.php` (`description`, `keywords`,
|
|
`mail_sender`, `mail_from`), footers, the "System check" page
|
|
* Links to the previous build owner's sites (`hostinpl.ru`, `osmp.ga`) replaced with `redl.io`
|
|
* "VK community" links pointing at somebody else's community replaced with `redl.io`
|
|
(`footer.php`, `views/main/index.php`, `views/offline/index.php`)
|
|
* All 15 e-mail templates: a "REDL.IO / AI-powered hosting" header and the signature
|
|
"Sincerely, the REDL.IO team"
|
|
|
|
**The authors' copyright headers were not removed** — they remain in every file that had them.
|
|
|
|
---
|
|
|
|
## 10. phpMyAdmin
|
|
|
|
The panel links to `/phpmyadmin` from the admin area. The original installer set phpMyAdmin up
|
|
through Apache, which did not work under nginx. It is now installed from the distribution repository
|
|
and served by nginx itself.
|
|
|
|
> **Reverse proxy pitfall.** For the address `/phpmyadmin` (without a trailing slash) nginx replies
|
|
> with a redirect and by default puts **its own port** into it, for example
|
|
> `http://panel.example.com:8095/phpmyadmin/`. If the panel sits behind a reverse proxy that address
|
|
> is unreachable from outside and the admin link does not open. The cure is
|
|
> `absolute_redirect off; port_in_redirect off;`, already present in the configuration the installer
|
|
> generates.
|
|
|
|
---
|
|
|
|
## What we did NOT do
|
|
|
|
* We did not rewrite password hashing. The panel stores passwords as **unsalted MD5**
|
|
(`md5($password)`, a `varchar(32)` column). Moving to `password_hash()` affects login,
|
|
registration, recovery and password changes, and requires migrating existing users.
|
|
* We did not move game server creation away from Docker.
|
|
* We did not delete `panel/application/public/js/proxy/proxy.php` — the file is defanged but kept as
|
|
evidence (see [SECURITY.en.md](SECURITY.en.md)).
|
|
* We did not test the payment gateways with real payments, nor VK login.
|
|
* We did not verify the game servers themselves: that needs a node with Docker.
|
|
|
|
---
|
|
|
|
**Maintained and developed with [REDL.IO](https://redl.io) — AI-powered hosting.**
|