Двуязычный интерфейс (RU/EN) с автоопределением + документация на двух языках
Панель: - 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 - переключатели языка в начале каждого документа - раздел «Язык интерфейса» в инструкции: как работает, как добавить свой язык
This commit is contained in:
+220
@@ -0,0 +1,220 @@
|
||||
[Русский](ИЗМЕНЕНИЯ.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.**
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
[Русский](ИНСТРУКЦИЯ.md) · **English**
|
||||
|
||||
# Complete guide
|
||||
|
||||
From an empty server to a hosting service that can take clients.
|
||||
|
||||
**Maintained and developed with [REDL.IO](https://redl.io) — AI-powered hosting.**
|
||||
|
||||
Contents:
|
||||
|
||||
1. [How it all fits together](#1-how-it-all-fits-together)
|
||||
2. [Installing the panel](#2-installing-the-panel)
|
||||
3. [Installing a game node](#3-installing-a-game-node)
|
||||
4. [Connecting the node to the panel](#4-connecting-the-node-to-the-panel)
|
||||
5. [Game server files](#5-game-server-files)
|
||||
6. [Configuring the panel](#6-configuring-the-panel)
|
||||
7. [Interface language](#7-interface-language)
|
||||
8. [Captcha](#8-captcha)
|
||||
9. [HTTPS and domain](#9-https-and-domain)
|
||||
10. [Maintenance](#10-maintenance)
|
||||
11. [Troubleshooting](#11-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## 1. How it all fits together
|
||||
|
||||
Two roles, usually on separate machines:
|
||||
|
||||
**The panel** — the website, the database, client areas, tickets, payments. It does not need Docker
|
||||
and runs happily on a cheap VPS.
|
||||
|
||||
**A game node (location)** — where the game servers themselves run. The panel connects to it
|
||||
**over SSH** and creates a separate Docker container for every ordered server:
|
||||
|
||||
```
|
||||
docker create --tty --rm --name=gs<ID> --network=host \
|
||||
--cpus="<cores>" --memory=<RAM>M \
|
||||
--volume="/home/gs<ID>/:/home/container/" \
|
||||
--workdir=/home/container debian:stretch
|
||||
```
|
||||
|
||||
Hence two hard requirements for a node: **working Docker** and **a local image tagged
|
||||
`debian:stretch`** (the name is hard-coded in the panel). Server files live on the node in
|
||||
`/home/gs<ID>` and appear inside the container as `/home/container`.
|
||||
|
||||
You can have several nodes — they are added to the panel as separate locations, and the client
|
||||
picks one when ordering.
|
||||
|
||||
> **Important about nodes.** Docker does not start inside container-based VPSes (LXC, OpenVZ and
|
||||
> similar) because the kernel forbids nested namespaces. A node needs **a dedicated server or KVM**.
|
||||
> The installer checks this first and warns you.
|
||||
|
||||
---
|
||||
|
||||
## 2. Installing the panel
|
||||
|
||||
Ubuntu 24.04 / 22.04 or Debian 12 / 13. Minimum: 1 core, 1 GB RAM, 10 GB disk.
|
||||
|
||||
```bash
|
||||
apt-get update && apt-get install -y git
|
||||
git clone https://github.com/RedlHosting/redl-gamepanel.git
|
||||
cd redl-gamepanel
|
||||
sudo bash install-panel.sh
|
||||
```
|
||||
|
||||
The script asks three things:
|
||||
|
||||
* **the panel's domain or IP** — it goes into the configuration as the base address, and all links
|
||||
and e-mails are built from it;
|
||||
* **the administrator e-mail** — also the login;
|
||||
* **the administrator password** — at least 6 characters.
|
||||
|
||||
Everything after that is automatic: packages, a database with a random password, the schema
|
||||
(27 tables), configuration, permissions, PHP, nginx, the scheduler, an autostart watchdog, an
|
||||
administrator with full access, and a final check that the login page opens.
|
||||
|
||||
It takes 2–5 minutes. The panel address and login are printed at the end.
|
||||
|
||||
**A non-standard port** (if 80 is taken, or the panel sits behind a reverse proxy):
|
||||
|
||||
```bash
|
||||
sudo PORT=8095 bash install-panel.sh
|
||||
```
|
||||
|
||||
**Where things end up:**
|
||||
|
||||
| What | Path |
|
||||
|------|------|
|
||||
| Panel code | `/var/www/hostinpl` |
|
||||
| Configuration | `/var/www/hostinpl/application/config.php` |
|
||||
| Database password and scheduler token | `/root/.redl-panel-credentials` |
|
||||
| nginx configuration | `/etc/nginx/sites-available/hostinpl` |
|
||||
| Scheduler | `/etc/cron.d/hostinpl` |
|
||||
| Autostart watchdog | `/usr/local/bin/hostinpl-guard` |
|
||||
| Database | MariaDB, database `hostin` |
|
||||
|
||||
**phpMyAdmin** (optional; the panel links to it from the admin area):
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y phpmyadmin # do NOT let it configure a web server, decline
|
||||
sudo ln -sfn /usr/share/phpmyadmin /var/www/hostinpl/phpmyadmin
|
||||
```
|
||||
|
||||
Log in with the database user from `/root/.redl-panel-credentials`. Remember that phpMyAdmin will be
|
||||
reachable by anyone who knows the address: either restrict it by IP in nginx, or do not install it.
|
||||
|
||||
---
|
||||
|
||||
## 3. Installing a game node
|
||||
|
||||
You need **a dedicated server or KVM** (not LXC/OpenVZ). Minimum: 2 cores, 2 GB RAM, 100 GB disk,
|
||||
counting 1–2 GB per game server.
|
||||
|
||||
```bash
|
||||
apt-get update && apt-get install -y git
|
||||
git clone https://github.com/RedlHosting/redl-gamepanel.git
|
||||
cd redl-gamepanel
|
||||
sudo bash install-node.sh
|
||||
```
|
||||
|
||||
The script asks:
|
||||
|
||||
* **this node's IP** — how the panel will reach it;
|
||||
* **whether to allow root SSH login with a password** — the panel can only connect with a login and
|
||||
password, and its commands require root. Answer `yes` and the script generates a strong root
|
||||
password and shows it at the end.
|
||||
|
||||
What it does: checks that the machine is suitable, installs Docker from the official repository,
|
||||
builds the `debian:stretch` image (Debian 12 inside, plus 32-bit libraries, Java, Node.js and
|
||||
screen), creates `/home/cp/gameservers/files` and the `gameservers` group, installs MariaDB for game
|
||||
server databases, SteamCMD and ProFTPD, configures sshd, and installs a Docker watchdog.
|
||||
|
||||
The first run takes 5–15 minutes, mostly building the image.
|
||||
|
||||
**Close the ports immediately afterwards.** The script prints ready-made commands; substitute the
|
||||
panel's address:
|
||||
|
||||
```bash
|
||||
ufw allow from PANEL_IP to any port 22 proto tcp
|
||||
ufw allow from PANEL_IP to any port 3306 proto tcp
|
||||
ufw deny 3306
|
||||
# leave the game ports open: 7777 (SA-MP), 22005 (RAGE:MP), 25565 (Minecraft) and so on
|
||||
```
|
||||
|
||||
If you also connect to the node over SSH yourself, **allow your own IP first** or you will lock
|
||||
yourself out:
|
||||
|
||||
```bash
|
||||
ufw allow from YOUR_IP to any port 22 proto tcp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Connecting the node to the panel
|
||||
|
||||
1. Sign in to the panel as an administrator
|
||||
2. Go to **Admin → Locations → Add location**
|
||||
3. Fill in:
|
||||
|
||||
| Field | What to enter |
|
||||
|-------|---------------|
|
||||
| Name | Anything meaningful: "Frankfurt", "Helsinki" |
|
||||
| IP | The node's IP |
|
||||
| User | `root` |
|
||||
| Password | The root password printed by `install-node.sh` |
|
||||
| Games | Tick the games available on this node |
|
||||
|
||||
4. Save and open **Admin → Locations** — if the panel connected, the location shows its real cores,
|
||||
RAM and disk (refreshed hourly by the scheduler).
|
||||
|
||||
If it does not connect, check from the panel machine:
|
||||
|
||||
```bash
|
||||
ssh root@NODE_IP # does the password work?
|
||||
php -m | grep ssh2 # is the php-ssh2 extension installed?
|
||||
```
|
||||
|
||||
If the extension is missing: `apt-get install -y php-ssh2`, then restart PHP-FPM.
|
||||
|
||||
---
|
||||
|
||||
## 5. Game server files
|
||||
|
||||
**They are not included.** The original installer downloaded them from third-party sites over plain
|
||||
HTTP with no signature checking, so we removed that (details in [SECURITY.en.md](SECURITY.en.md)).
|
||||
|
||||
Lay the files out on the node like this:
|
||||
|
||||
```
|
||||
/home/cp/gameservers/files/<game_code>/
|
||||
```
|
||||
|
||||
Game codes are listed under **Admin → Games** (for example `samp`, `crmp`, `mta`, `minecraft`,
|
||||
`cs`, `css`, `ragemp`). When a server is created, the panel copies the contents of that directory
|
||||
into `/home/gs<ID>/`.
|
||||
|
||||
Only take builds from primary sources: SA-MP from `sa-mp.com`, MTA from `mtasa.com`,
|
||||
Minecraft (Paper) from `papermc.io`, RAGE:MP from `rage.mp`, Steam games via SteamCMD
|
||||
(already installed in `/root/steamcmd`).
|
||||
|
||||
Then test the whole chain: order a server as a test client and confirm that it is created and starts.
|
||||
|
||||
---
|
||||
|
||||
## 6. Configuring the panel
|
||||
|
||||
**Admin → Settings**, four tabs:
|
||||
|
||||
* **General settings** — name, description, contacts, logo
|
||||
* **Payment gateways** — Unitpay, Enot, AnyPay, LiteKassa, RoboKassa, FreeKassa, YooKassa, QIWI.
|
||||
You need your own merchant accounts. We never tested a gateway with a real payment — test each one
|
||||
with your own money before taking clients.
|
||||
* **Other settings** — trial period, e-mail confirmation, **captcha**, maintenance mode, VK login
|
||||
* **Information and bonuses** — top-up bonuses, referral percentages
|
||||
|
||||
Plans and games live under **Admin → Games** (versions, RAM and core limits, prices).
|
||||
|
||||
> When you save settings the panel rewrites `config.php` entirely. It matches configuration lines
|
||||
> **by substring**, so do not create parameters whose names are contained in other names.
|
||||
> After editing the file by hand, restart PHP-FPM **fully** (`restart`, not `reload`) — otherwise a
|
||||
> cached copy is served.
|
||||
|
||||
---
|
||||
|
||||
## 7. Interface language
|
||||
|
||||
The panel speaks **Russian and English**. The language is chosen in this order:
|
||||
|
||||
1. `?lang=en` or `?lang=ru` in the URL — an explicit choice, remembered in a cookie for a year
|
||||
2. the `lang` cookie — the previous choice
|
||||
3. **the browser's `Accept-Language` header** — automatic detection
|
||||
4. the `lang` value in `application/config.php` (default `ru`)
|
||||
|
||||
Automatic detection deliberately keeps Russian for Russian-speaking locales (ru, uk, be, kk and
|
||||
others) and switches to English for everything else, so a visitor from Germany or Brazil gets the
|
||||
English interface without touching anything.
|
||||
|
||||
**The switcher** sits in the top bar of the client area and the admin area (the `RU` / `EN` buttons
|
||||
next to the balance) and in the footer of the login page.
|
||||
|
||||
**How it is built.** The panel was written with Russian text directly in the templates — about
|
||||
1500 strings across 200 files. Instead of rewriting all of them, translation happens on the finished
|
||||
response: `index.php` wraps the output in `ob_start()`, and before the page is sent to the browser
|
||||
the strings are replaced using a dictionary (`engine/main/lang.php`). This covers the whole panel at
|
||||
once, including the admin area and e-mails, and anything missing from the dictionary simply stays
|
||||
Russian — nothing can vanish from the screen.
|
||||
|
||||
Replacement only happens on word boundaries, so a short dictionary key cannot corrupt a longer word.
|
||||
|
||||
**Adding your own language**, for example German:
|
||||
|
||||
```bash
|
||||
cp panel/application/lang/en.php panel/application/lang/de.php
|
||||
# translate the values in de.php, then add 'de' to Lang::AVAILABLE
|
||||
# in panel/engine/main/lang.php
|
||||
```
|
||||
|
||||
Dictionary coverage is verified by crawling the pages: all 20 sections we walk through render with
|
||||
no Russian left over. If you spot an untranslated phrase, add it to `application/lang/en.php` —
|
||||
the key is the Russian text exactly as it appears.
|
||||
|
||||
Translation only runs when the language is not Russian; in Russian mode the dictionary is not even
|
||||
loaded, so there is no overhead.
|
||||
|
||||
---
|
||||
|
||||
## 8. Captcha
|
||||
|
||||
By default it is **off** — registration and login work without it, and the field is simply absent.
|
||||
|
||||
To enable it:
|
||||
|
||||
1. Get **reCAPTCHA v2 "I'm not a robot"** keys at
|
||||
[google.com/recaptcha/admin](https://www.google.com/recaptcha/admin) and specify the panel's domain
|
||||
2. Go to **Admin → Settings → Other settings → "Bot protection (reCAPTCHA v2)"**
|
||||
3. Paste the **Site key** and the **Secret key**
|
||||
4. Switch to "Enabled" and save
|
||||
|
||||
**The order matters.** Enable the captcha without keys and the form will show
|
||||
"ERROR for site owner: Invalid site key", locking everyone out. If that happens, open
|
||||
`/var/www/hostinpl/application/config.php`, set `'captcha_enable' => '0'` and restart PHP-FPM:
|
||||
|
||||
```bash
|
||||
sudo sed -i "s/'captcha_enable' => '1'/'captcha_enable' => '0'/" /var/www/hostinpl/application/config.php
|
||||
sudo service php8.4-fpm restart # use your PHP version
|
||||
```
|
||||
|
||||
The captcha covers login, registration, password recovery and ticket creation — all four forms at once.
|
||||
|
||||
---
|
||||
|
||||
## 9. HTTPS and domain
|
||||
|
||||
The panel is installed over HTTP. For a certificate:
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d panel.example.com
|
||||
```
|
||||
|
||||
After the certificate is issued, update the address in the configuration, otherwise links in
|
||||
e-mails and redirects will stay on `http://`:
|
||||
|
||||
```bash
|
||||
sudo sed -i "s|'url' => 'http://|'url' => 'https://|" /var/www/hostinpl/application/config.php
|
||||
sudo service php8.4-fpm restart
|
||||
```
|
||||
|
||||
If the panel sits behind a reverse proxy (Caddy, nginx, Cloudflare), its nginx configuration already
|
||||
contains `absolute_redirect off; port_in_redirect off;` — without them redirects point at the
|
||||
internal port and links such as `/phpmyadmin` do not open from outside.
|
||||
|
||||
---
|
||||
|
||||
## 10. Maintenance
|
||||
|
||||
**Backups.** Keep the database and the configuration:
|
||||
|
||||
```bash
|
||||
mysqldump hostin > /root/hostin-$(date +%F).sql
|
||||
cp /var/www/hostinpl/application/config.php /root/config-$(date +%F).php
|
||||
```
|
||||
|
||||
Game server files live on the node in `/home/gs<ID>` — back those up on the node.
|
||||
|
||||
**Logs:**
|
||||
|
||||
| What to look at | Where |
|
||||
|-----------------|-------|
|
||||
| PHP and nginx errors | `/var/log/nginx/error.log` |
|
||||
| Scheduler hits | `grep 'main/cron' /var/log/nginx/access.log` |
|
||||
| Login journal | the `authlog` table |
|
||||
| Node image build | `/tmp/redl-docker-build.log` |
|
||||
|
||||
**Health check:** **Admin → System check** — PHP extensions, versions, scheduler links. Every item
|
||||
should be green.
|
||||
|
||||
**Updating the code** from the repository:
|
||||
|
||||
```bash
|
||||
cd redl-gamepanel && git pull
|
||||
sudo cp -a panel/application panel/engine /var/www/hostinpl/ # config.php is not overwritten
|
||||
sudo chown -R www-data:www-data /var/www/hostinpl
|
||||
sudo service php8.4-fpm restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Troubleshooting
|
||||
|
||||
**"Error: could not load the controller …"** — the panel found no controller for that address.
|
||||
Usually it is a link to something that does not exist, for example `/phpmyadmin` when phpMyAdmin is
|
||||
not installed (see section 2). If the section really should exist, check that the files are in place
|
||||
and owned by `www-data`, then restart PHP-FPM **fully**: opcache may have cached a file that was
|
||||
caught mid-write.
|
||||
|
||||
**The login page is blank or has no form** — look at `/var/log/nginx/error.log`. A common cause is
|
||||
`short_open_tag = Off`. The panel uses short `<? ?>` tags; the installer turns the option on, but you
|
||||
have to restore it if you reinstall PHP.
|
||||
|
||||
**Cannot log in, the form shows "Invalid site key"** — the captcha is on without keys, see section 8.
|
||||
|
||||
**The scheduler is not running** (statistics not updating, expired servers not suspended):
|
||||
|
||||
```bash
|
||||
grep -c 'main/cron' /var/log/nginx/access.log # should grow
|
||||
pgrep -a cron # is the daemon running?
|
||||
cat /etc/cron.d/hostinpl # every job must be on ONE line
|
||||
```
|
||||
|
||||
If a job is split across two lines, the token got into the file with a newline — rewrite the file,
|
||||
taking the token from `/root/.redl-panel-credentials`.
|
||||
|
||||
**A server is not created on the node** — check on the node:
|
||||
|
||||
```bash
|
||||
docker info # is the daemon up?
|
||||
docker image inspect debian:stretch # is the image there?
|
||||
docker run --rm debian:stretch echo ok # does a container start?
|
||||
ls /home/cp/gameservers/files/ # are the game files in place?
|
||||
```
|
||||
|
||||
**The server is created but dies immediately** — look at the container log on the node:
|
||||
`docker logs gs<ID>`. Most often the 32-bit libraries are missing (rebuild the image from our
|
||||
`docker/Dockerfile`) or the game build is damaged.
|
||||
|
||||
**Everything worked until a reboot** — on machines without systemd, the watchdogs on cron bring
|
||||
things back. Check `pgrep -a cron` and run `/usr/local/bin/hostinpl-guard` by hand
|
||||
(`gamenode-guard` on a node).
|
||||
|
||||
**A phrase in the interface is still Russian in English mode** — add it to
|
||||
`panel/application/lang/en.php`; the key is the Russian text exactly as it appears on the page.
|
||||
|
||||
---
|
||||
|
||||
**Maintained and developed with [REDL.IO](https://redl.io) — AI-powered hosting.**
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
[Русский](README.md) · **English**
|
||||
|
||||
# Game Hosting Control Panel · REDL build
|
||||
|
||||
A control panel for selling and managing game servers: SA-MP, CRMP, MTA, Minecraft, CS 1.6, CS:S, RAGE:MP.
|
||||
Client area, ticket system, payment gateways, automated game server deployment onto game nodes.
|
||||
|
||||
**Maintained and developed with [REDL.IO](https://redl.io) — AI-powered hosting.**
|
||||
|
||||
This build is a modernisation of the HostinPL 5.6 panel. The original was written for Debian 9 and PHP 7.0
|
||||
(both end-of-life since 2022) and simply would not start on a current server. Here it runs on an up-to-date
|
||||
stack, the vulnerabilities we found are fixed, and one-click installers are included.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Legal status — read this before installing
|
||||
|
||||
The original **HostinPL 5.6 is a commercial, proprietary product**. Its authors are Samir Shelenko and
|
||||
Alexander Zemlyanoy. The file `panel/LICENSE` is their licence, and it grants no permission to
|
||||
redistribute or modify the software.
|
||||
|
||||
The sources this build is based on were circulated as "nulled" — a cracked copy of a paid product,
|
||||
published without the authors' consent. We are not hiding that or rewriting history: the authors'
|
||||
copyright headers are preserved throughout the code and were deliberately not removed.
|
||||
|
||||
What this means in practice:
|
||||
|
||||
* using it for commercial hosting infringes copyright, with all the risks that follow;
|
||||
* the legal route is to buy a licence from the authors, or use an open-source alternative
|
||||
(Pterodactyl, Pelican);
|
||||
* this repository is only useful for studying the code, auditing it, and internal experiments.
|
||||
|
||||
Responsibility for use rests with whoever installs it.
|
||||
|
||||
---
|
||||
|
||||
## What is in this repository
|
||||
|
||||
| Path | What it is |
|
||||
|------|------------|
|
||||
| `install-panel.sh` | One-click panel install (nginx + PHP 8 + MariaDB + scheduler + admin user) |
|
||||
| `install-node.sh` | One-click game node install (Docker + image + directories + SSH/FTP + SteamCMD) |
|
||||
| `panel/` | Panel source code with all of our changes |
|
||||
| `docker/Dockerfile` | Modern game server image (Debian 12; the original stretch-based one no longer builds) |
|
||||
| `docker/Dockerfile.original-stretch` | The original Dockerfile, kept for comparison |
|
||||
| [`CHANGES.en.md`](CHANGES.en.md) | Full list of differences from the original |
|
||||
| [`SECURITY.en.md`](SECURITY.en.md) | Audit results: vulnerabilities, backdoors, what was fixed |
|
||||
| [`GUIDE.en.md`](GUIDE.en.md) | Complete guide: install, connecting a node, configuration, maintenance |
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
**For the panel** (the website and database — Docker is not needed):
|
||||
|
||||
* Ubuntu 24.04 / 22.04 or Debian 12 / 13
|
||||
* 1 core, 1 GB RAM, 10 GB disk
|
||||
* Any VPS will do, including container-based ones (LXC/OpenVZ)
|
||||
|
||||
**For a game node** (this is where the game servers actually run):
|
||||
|
||||
* Ubuntu 24.04 / 22.04 or Debian 12 / 13
|
||||
* 2 cores, 2 GB RAM, 100 GB disk (budget 1–2 GB per game server)
|
||||
* **Full kernel access is mandatory: a dedicated server or KVM.**
|
||||
The panel creates a separate Docker container for every game server, and Docker does not start
|
||||
inside a container-based VPS (LXC/OpenVZ) — the kernel forbids nested namespaces. The installer
|
||||
checks for this and tells you plainly if the machine is unsuitable.
|
||||
|
||||
The panel and a node can share one machine, but separating them is better: keep the website on a
|
||||
cheap VPS and put the games on real hardware.
|
||||
|
||||
---
|
||||
|
||||
## One-click install
|
||||
|
||||
### Panel
|
||||
|
||||
```bash
|
||||
apt-get update && apt-get install -y git
|
||||
git clone https://github.com/RedlHosting/redl-gamepanel.git
|
||||
cd redl-gamepanel
|
||||
sudo bash install-panel.sh
|
||||
```
|
||||
|
||||
The script asks only for the panel address, the administrator e-mail and password — everything else
|
||||
is automatic: it installs nginx, PHP 8 and MariaDB, creates a database with a random password, loads
|
||||
the schema, writes the configuration and the scheduler, sets up an autostart watchdog, creates the
|
||||
administrator, and verifies that the login page actually opens.
|
||||
|
||||
To run the panel on a non-standard port, set it via an environment variable:
|
||||
|
||||
```bash
|
||||
sudo PORT=8095 bash install-panel.sh
|
||||
```
|
||||
|
||||
### Game node
|
||||
|
||||
```bash
|
||||
git clone https://github.com/RedlHosting/redl-gamepanel.git
|
||||
cd redl-gamepanel
|
||||
sudo bash install-node.sh
|
||||
```
|
||||
|
||||
At the end the script prints the IP, SSH login and password — enter those in the panel under
|
||||
**Admin → Locations → Add location**. See [GUIDE.en.md](GUIDE.en.md) for details.
|
||||
|
||||
---
|
||||
|
||||
## What already works
|
||||
|
||||
* Login, registration, password recovery — **without a captcha** (it is enabled from the admin area, see below)
|
||||
* Client area: balance, invoices, transfers, bonuses, promo codes, referral system
|
||||
* Ticket system with categories and attachments
|
||||
* Admin area: users, servers, locations, games, news, statistics, promo codes, settings
|
||||
* System check (Admin → System check) — every item green
|
||||
* Payment gateways: Unitpay, Enot, AnyPay, LiteKassa, RoboKassa, FreeKassa, YooKassa, QIWI
|
||||
(credentials are entered in the admin area; we did not test any gateway with a real payment)
|
||||
* Web hosting (the WEB section) — via ISPmanager on a separate machine
|
||||
* Scheduler: statistics, tasks, automatic suspension of expired servers
|
||||
* **Two interface languages: Russian and English**, with automatic detection from the browser
|
||||
and a switcher in the header (see [GUIDE.en.md](GUIDE.en.md))
|
||||
|
||||
### The captcha is off and is enabled with a single switch
|
||||
|
||||
By default there is no captcha anywhere — not on login, registration or tickets; the field simply
|
||||
does not exist. When you need it:
|
||||
|
||||
1. Get reCAPTCHA v2 ("I'm not a robot") keys at [google.com/recaptcha](https://www.google.com/recaptcha/admin)
|
||||
2. Go to **Admin → Settings → Other settings → "Bot protection (reCAPTCHA v2)"**
|
||||
3. Paste the Site key and the Secret key, switch it to "Enabled", save
|
||||
|
||||
The order matters: keys first, switch second — otherwise the form shows
|
||||
"ERROR for site owner: Invalid site key" and nobody can log in.
|
||||
|
||||
## What does not work
|
||||
|
||||
* **Creating game servers on a container-based VPS.** You need a node with real Docker
|
||||
(dedicated or KVM) — see Requirements for why.
|
||||
* **Game server files are not included.** The original installer downloaded them from third-party
|
||||
sites over plain HTTP with no signature checking, so we removed that (see [SECURITY.en.md](SECURITY.en.md)).
|
||||
You need to place the files into `/home/cp/gameservers/files/<game_code>/` yourself.
|
||||
* **Payments and VK login** require your own keys and merchant accounts.
|
||||
|
||||
---
|
||||
|
||||
## Differences from the original, in brief
|
||||
|
||||
* Runs on **PHP 8.4** instead of PHP 7.0 — a walk through all 21 sections of the panel produces
|
||||
zero errors in the log
|
||||
* **The installer was rewritten from scratch**: the original overwrote `/etc/apt/sources.list` with
|
||||
Debian 9 repositories and broke apt on any current system
|
||||
* **Two pre-authentication SQL injections were fixed**, and plaintext password storage was removed
|
||||
* The captcha became manageable from the admin area
|
||||
* Russian and English interface with automatic language detection
|
||||
* The scheduler and autostart work even where systemd is absent
|
||||
|
||||
The full list is in [CHANGES.en.md](CHANGES.en.md) and [SECURITY.en.md](SECURITY.en.md).
|
||||
|
||||
---
|
||||
|
||||
**Maintained and developed with [REDL.IO](https://redl.io) — AI-powered hosting.**
|
||||
@@ -1,3 +1,5 @@
|
||||
**Русский** · [English](README.en.md)
|
||||
|
||||
# Панель управления игровым хостингом · сборка REDL
|
||||
|
||||
Панель для продажи и управления игровыми серверами: SA-MP, CRMP, MTA, Minecraft, CS 1.6, CS:S, RAGE:MP.
|
||||
@@ -114,6 +116,8 @@ sudo bash install-node.sh
|
||||
(реквизиты вписываются в админке; ни один шлюз мы не тестировали живыми платежами)
|
||||
* Веб-хостинг (раздел WEB) — через ISPmanager на отдельной машине
|
||||
* Планировщик: статистика, задачи, автоотключение просроченных серверов
|
||||
* **Два языка интерфейса: русский и английский**, с автоопределением по браузеру
|
||||
и переключателем в шапке (см. [ИНСТРУКЦИЯ.md](ИНСТРУКЦИЯ.md))
|
||||
|
||||
### Капча — выключена, включается одним переключателем
|
||||
|
||||
@@ -145,6 +149,7 @@ sudo bash install-node.sh
|
||||
Debian 9 и ломал apt на любой современной системе
|
||||
* **Закрыты две SQL-инъекции, доступные без авторизации**, и убрано хранение паролей в открытом виде
|
||||
* Капча стала управляемой из админки
|
||||
* Русский и английский интерфейс с автоопределением языка
|
||||
* Планировщик и автозапуск работают и там, где нет systemd
|
||||
|
||||
Полностью — в [ИЗМЕНЕНИЯ.md](ИЗМЕНЕНИЯ.md) и [БЕЗОПАСНОСТЬ.md](БЕЗОПАСНОСТЬ.md).
|
||||
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
[Русский](БЕЗОПАСНОСТЬ.md) · **English**
|
||||
|
||||
# Security audit: what we found and what we fixed
|
||||
|
||||
An audit of the HostinPL 5.6 build (a "nulled" copy). We looked for hidden backdoors and webshells,
|
||||
obfuscation, data being sent to third-party hosts, SQL injections, RCE, authorisation bypasses,
|
||||
password storage, and problems in the installer and the Dockerfile.
|
||||
|
||||
**Maintained and developed with [REDL.IO](https://redl.io) — AI-powered hosting.**
|
||||
|
||||
---
|
||||
|
||||
## The short version
|
||||
|
||||
The fork claimed: "all shells/holes have been cut out of the panel". Our check shows that claim is
|
||||
**only partly true**. We found no working backdoors, and there is no obfuscation in the panel code.
|
||||
But we did find:
|
||||
|
||||
* **proof that webshells really were present in this lineage** — one was found, already defanged;
|
||||
* **two SQL injections reachable without authentication** — not mentioned anywhere in the fork's description;
|
||||
* **user passwords written to the database in plaintext**;
|
||||
* code being installed onto game nodes **over plain HTTP with no signature verification**.
|
||||
|
||||
We fixed the first three. Details below.
|
||||
|
||||
---
|
||||
|
||||
## 1. A webshell in the build's lineage — defanged
|
||||
|
||||
**File:** `panel/application/public/js/proxy/proxy.php` — all 7 lines of it:
|
||||
|
||||
```php
|
||||
<?php
|
||||
if(isset($_GET['cmd'])){
|
||||
print('Backdoor fixed by Xopowblu-4EJlOBEK aka Und3X (und3x.ru)');
|
||||
}else{
|
||||
header("Location: /");
|
||||
}
|
||||
?>
|
||||
```
|
||||
|
||||
The file takes a `?cmd=` parameter — the classic signature of a command-accepting webshell. Here the
|
||||
body has been replaced with a message, so **this particular shell is defanged**. But the implication
|
||||
is unambiguous: backdoors were planted deliberately in this build's distribution chain, and one of
|
||||
them sat in a scripts folder nobody normally looks at.
|
||||
|
||||
**Our decision:** the file is kept as evidence. It executes nothing. If you do not want it:
|
||||
`rm panel/application/public/js/proxy/proxy.php`.
|
||||
|
||||
**Takeaway:** any other copy of this panel obtained anywhere but this repository may contain a
|
||||
*working* shell. Before installing someone else's build, check at least this much:
|
||||
|
||||
```bash
|
||||
grep -rIn --include=*.php -E "eval\(|assert\(|base64_decode|gzinflate|\\\$_REQUEST\[" .
|
||||
find . -path ./engine/libs -prune -o -name '*.php' -print | grep -E '(assets|public|tmp)/'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. What we did not find (the good news)
|
||||
|
||||
Checked specifically, result negative:
|
||||
|
||||
* **No obfuscation in the panel code.** Zero occurrences of `eval`, `assert`, `create_function`,
|
||||
`gzinflate`, `$$` or `$_REQUEST` outside vendor libraries. Not a single long base64 blob.
|
||||
* **No stray PHP files** in the asset, upload or temporary directories — apart from the `proxy.php`
|
||||
described above.
|
||||
* **No data leaking to the author's hosts.** `vipadmin.club` appeared only in the keywords meta tag
|
||||
and the mail sender address; `osmp.ga` was a footer link. Nothing sends passwords, licence data or
|
||||
database credentials anywhere.
|
||||
* **The admin gate is intact.** Every controller under `application/controllers/admin/**` performs a
|
||||
`getAccessLevel()` check (level 2 for the admin area, 3 for games and locations). Not one file
|
||||
without it.
|
||||
* **The panel never invokes a shell directly.** A naive `grep` reports 54 `exec` and 8 `system`, but
|
||||
all of them are inside vendor libraries: in phpseclib, `exec()` is an SSH method rather than a
|
||||
process launcher, and the rest is in elFinder. The panel's own code calls no shell.
|
||||
|
||||
---
|
||||
|
||||
## 3. Two pre-authentication SQL injections — FIXED
|
||||
|
||||
The most serious finding, and one the fork's description says nothing about.
|
||||
|
||||
**File:** `panel/application/models/users.php`, function `createAuthLog()`.
|
||||
|
||||
The function records login attempts. It used to interpolate values straight into SQL:
|
||||
|
||||
```php
|
||||
$query=$this->db->query("INSERT INTO `authlog` (... ,`ip`, ... ,`password`)
|
||||
VALUES (NULL, '".$userid."', '".$ip."', ... , '".$password."');");
|
||||
```
|
||||
|
||||
**Vector 1 — via the login form.** `$password` is the raw password from `$_POST`, unescaped. It is
|
||||
called from `account/login.php` on **every** login attempt, including failed ones — that is, **before
|
||||
any authentication**. Anybody off the street could inject SQL through the login form.
|
||||
|
||||
**Vector 2 — via a forged header.** `$ip` came from `getRealIpAdress()` (`engine/main/user.php`),
|
||||
which returned the `CF-Connecting-IP` header **with no validation at all** — the early `return` sat
|
||||
before the `filter_var` block:
|
||||
|
||||
```php
|
||||
if (!empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
|
||||
return $_SERVER["HTTP_CF_CONNECTING_IP"]; // as-is, unvalidated
|
||||
}
|
||||
```
|
||||
|
||||
That header is supplied by the client, so an attacker fully controlled the value reaching SQL.
|
||||
The same unvalidated IP was also sent to the external `ip-api.com` service.
|
||||
|
||||
**What we did:**
|
||||
|
||||
* every value in the `INSERT` now goes through `$this->db->escape()` or is cast to `(int)`;
|
||||
* `CF-Connecting-IP` is validated with `filter_var($ip, FILTER_VALIDATE_IP)` and otherwise ignored
|
||||
in favour of `REMOTE_ADDR`;
|
||||
* the `ip-api.com` request only runs for a valid IP and uses `urlencode()`.
|
||||
|
||||
**Verified on a live installation:** injection attempts through the password field and through a
|
||||
forged header no longer get through, the `authlog` table is intact, and all 27 tables are present.
|
||||
|
||||
---
|
||||
|
||||
## 4. Plaintext passwords — FIXED
|
||||
|
||||
`account/login.php` passed the **real password** from the form into `createAuthLog()`:
|
||||
|
||||
```php
|
||||
$this->usersModel->createAuthLog($userid['user_id'], $ip, '1', $password); // successful login
|
||||
$this->usersModel->createAuthLog($userid['user_id'], $ip, '0', $password); // and failed ones too
|
||||
```
|
||||
|
||||
So the `authlog` table accumulated every user's password in plaintext — including typos and
|
||||
passwords for other services that people enter by accident. Anyone with database access could read
|
||||
them, and given the injection in section 3, so could an outsider.
|
||||
|
||||
**What we did:** the password is no longer passed; an empty string is stored instead.
|
||||
Verified: after both a successful and a failed login, the column is empty.
|
||||
|
||||
---
|
||||
|
||||
## 5. XSS in the registration form — FIXED
|
||||
|
||||
`panel/application/views/common/loginheader.php`: the `?ref=` parameter (referral code) was echoed
|
||||
into a hidden form field without escaping — `<?echo $_GET['ref']?>`.
|
||||
`htmlspecialchars(..., ENT_QUOTES, 'UTF-8')` was added, and the controller casts the value to `(int)`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Passwords stored as unsalted MD5 — NOT fixed
|
||||
|
||||
`login.php:114` — `md5($password)`, column `user_password varchar(32)`.
|
||||
|
||||
Unsalted MD5 is brute-forced at billions of hashes per second on a consumer GPU, and rainbow tables
|
||||
for common passwords are freely available. If the database leaks, treat every user password as known.
|
||||
|
||||
We **did not change this**: moving to `password_hash()` touches login, registration, recovery and
|
||||
password changes, and requires migrating existing users — that is a project, not a one-line edit.
|
||||
|
||||
**What to do:** if the panel is used with real people, migrate to `password_hash()` transparently —
|
||||
when a user logs in successfully against the old MD5 hash, re-save it with the new algorithm.
|
||||
|
||||
---
|
||||
|
||||
## 7. Code installed onto game nodes over HTTP — source removed
|
||||
|
||||
`panel/engine/games/game_settings.php:54-69` — 18 Node.js modules for RAGE:MP are downloaded
|
||||
**over plain HTTP** from a third-party host, `mc.hostinpl.ru`, as zip archives with no signature and
|
||||
no checksum verification.
|
||||
|
||||
Any intermediary on the path — or the owner of that host — can replace the contents, and the code
|
||||
then runs on your game nodes. The original installer likewise pulled game server builds from
|
||||
`dl.und3x.ru` and `vipadmin.club`.
|
||||
|
||||
**What we did:** our node installer no longer downloads builds from those hosts — you place the
|
||||
files into `/home/cp/gameservers/files/<game_code>/` yourself. The lines in `game_settings.php` were
|
||||
left alone (that is panel functionality), but you should not rely on them: either replace the URLs
|
||||
with your own over HTTPS, or deploy the modules by hand.
|
||||
|
||||
Node.js in our `docker/Dockerfile` is installed from NodeSource **over HTTPS with repository key
|
||||
verification**.
|
||||
|
||||
---
|
||||
|
||||
## 8. The panel connects to nodes with the root password — by design
|
||||
|
||||
The `locations` table stores `location_user` and `location_password` (`varchar(32)`) **in plaintext**,
|
||||
and the connection uses `ssh2_auth_password()`. The panel's commands (`useradd`, `docker`,
|
||||
`chown /home`) require root, so in practice the panel logs into the node as root with a password.
|
||||
|
||||
This cannot be changed without reworking the panel: it supports neither keys nor `sudo`. Measures
|
||||
that reduce the risk:
|
||||
|
||||
```bash
|
||||
# on the node: SSH and MySQL reachable only from the panel's address
|
||||
ufw allow from PANEL_IP to any port 22 proto tcp
|
||||
ufw allow from PANEL_IP to any port 3306 proto tcp
|
||||
ufw deny 3306
|
||||
```
|
||||
|
||||
The node installer prints these commands when it finishes. Additionally: use a separate password per
|
||||
node, and keep access to the panel's database to a minimum — whoever reads `locations` gets root on
|
||||
every node.
|
||||
|
||||
---
|
||||
|
||||
## 9. MariaDB on the node listens on all interfaces — the panel requires it
|
||||
|
||||
The panel creates game server databases on the node itself and connects to them over the network,
|
||||
so `bind-address = 0.0.0.0` is mandatory. Our installer does that but **prints a warning** and the
|
||||
firewall commands — unlike the original, which changed the setting silently.
|
||||
|
||||
---
|
||||
|
||||
## 10. The registration e-mail contains the password
|
||||
|
||||
`panel/application/views/mail/account/register.php` sends the new user their password in plaintext by
|
||||
e-mail. That is the original design; we did not change it so as not to break the registration and
|
||||
activation flow. Bear in mind the message stays in the mailbox and on intermediate servers.
|
||||
|
||||
---
|
||||
|
||||
## Overall assessment
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| Working backdoors in the panel code | none found |
|
||||
| Webshell in the build's lineage | found already defanged, kept as evidence |
|
||||
| Obfuscation, data exfiltration | none found |
|
||||
| Admin authorisation bypass | none found |
|
||||
| Pre-auth SQL injections (2) | **fixed** |
|
||||
| Plaintext passwords in the database | **fixed** |
|
||||
| XSS in the registration form | **fixed** |
|
||||
| Unsalted MD5 | remains, needs separate work |
|
||||
| Code installed over HTTP onto nodes | source removed from the installer |
|
||||
| Root password used for node access | by design, mitigate with a firewall |
|
||||
| Password inside the registration e-mail | remains |
|
||||
|
||||
**This panel should not be used for commercial hosting with real clients and payments** — because of
|
||||
its legal status (see the README), because of the MD5 passwords, and because the provenance of the
|
||||
code does not deserve trust. For study, internal tasks and auditing, it is fine.
|
||||
|
||||
---
|
||||
|
||||
**Maintained and developed with [REDL.IO](https://redl.io) — AI-powered hosting.**
|
||||
@@ -61,6 +61,7 @@ $config = array(
|
||||
'serv_test' => '0',
|
||||
'offline' => '0',
|
||||
'offline_res' => 'Хостинг временно закрыт на обслуживание! ',
|
||||
'lang' => 'ru',
|
||||
'captcha_enable' => '0',
|
||||
'recaptcha' => 'ReCaptchaV2',
|
||||
'secret_recaptcha' => '2ReCaptchaV2',
|
||||
|
||||
@@ -0,0 +1,727 @@
|
||||
<?php
|
||||
/*
|
||||
Английский словарь интерфейса. Сборка REDL.
|
||||
Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом.
|
||||
|
||||
Ключ — исходная русская фраза как она встречается в шаблонах, значение — перевод.
|
||||
Замена идёт ТОЛЬКО на границах слов (см. engine/main/lang.php): ключ, стоящий
|
||||
внутри более длинного слова, не срабатывает — иначе «Мод» превращал «Модуль»
|
||||
в «Modуль». Из нескольких подходящих ключей выбирается самый длинный.
|
||||
|
||||
Поэтому:
|
||||
* порядок записей не важен;
|
||||
* для слова в разных формах нужны отдельные записи ('дней', 'дня', 'день') —
|
||||
словарь не знает морфологии;
|
||||
* фразы с подстановкой переменных ('Сервер $id') задавайте статическим
|
||||
началом строки — 'Сервер' — потому что в готовом ответе переменная
|
||||
уже раскрыта.
|
||||
|
||||
Чего нет в словаре — останется по-русски. Это осознанно: ничего не может
|
||||
исчезнуть с экрана из-за незаполненного перевода.
|
||||
*/
|
||||
|
||||
return array(
|
||||
|
||||
// ── общее: кнопки и действия ─────────────────────────────────────────────────
|
||||
'Отмена' => 'Cancel',
|
||||
'Сохранить изменения' => 'Save changes',
|
||||
'Сохранить' => 'Save',
|
||||
'Применить' => 'Apply',
|
||||
'Отправить' => 'Send',
|
||||
'Написать' => 'Write',
|
||||
'Открыть' => 'Open',
|
||||
'Установить' => 'Install',
|
||||
'Продлить' => 'Renew',
|
||||
'Оплатить' => 'Pay',
|
||||
'Пополнить баланс' => 'Top up balance',
|
||||
'Пополнить' => 'Top up',
|
||||
'Сменить' => 'Change',
|
||||
'Изменить пароль' => 'Change password',
|
||||
'Сменить пароль' => 'Change password',
|
||||
'Скачать файл' => 'Download file',
|
||||
'Распечатать' => 'Print',
|
||||
'Войти' => 'Log in',
|
||||
'Выйти' => 'Log out',
|
||||
'Создать аккаунт' => 'Create account',
|
||||
'Читать полностью' => 'Read more',
|
||||
'Оставить комментарий' => 'Leave a comment',
|
||||
'Перевести средства' => 'Transfer funds',
|
||||
'Обменять бонусы' => 'Exchange bonuses',
|
||||
'Управление' => 'Manage',
|
||||
'Выбор' => 'Selection',
|
||||
|
||||
// ── навигация и разделы ──────────────────────────────────────────────────────
|
||||
'Главная' => 'Home',
|
||||
'Сервера' => 'Servers',
|
||||
'Заказать Сервер' => 'Order a server',
|
||||
'Мои Сервера' => 'My servers',
|
||||
'Мои сервера' => 'My servers',
|
||||
'Список серверов' => 'Server list',
|
||||
'Заказать веб-хостинг' => 'Order web hosting',
|
||||
'Мои веб-сайты' => 'My websites',
|
||||
'Веб-хостинг: ws' => 'Web hosting: ws',
|
||||
'Тикеты' => 'Tickets',
|
||||
'Мои тикеты' => 'My tickets',
|
||||
'Мои запросы' => 'My requests',
|
||||
'Создать запрос' => 'New request',
|
||||
'Список запросов' => 'Request list',
|
||||
'Поддержка' => 'Support',
|
||||
'Служба поддержки' => 'Support team',
|
||||
'Техническая поддержка' => 'Technical support',
|
||||
'База знаний (FAQ)' => 'Knowledge base (FAQ)',
|
||||
'База знаний' => 'Knowledge base',
|
||||
'Финансы' => 'Billing',
|
||||
'Баланс' => 'Balance',
|
||||
'История баланса' => 'Balance history',
|
||||
'Список пополнений' => 'Top-up list',
|
||||
'История операций' => 'Transaction history',
|
||||
'Список операций' => 'Transaction list',
|
||||
'Перевод средств' => 'Funds transfer',
|
||||
'Обещанный платеж' => 'Deferred payment',
|
||||
'Партнерская программа' => 'Affiliate programme',
|
||||
'Профиль' => 'Profile',
|
||||
'Мой профиль' => 'My profile',
|
||||
'Личные данные' => 'Personal details',
|
||||
'Авторизация' => 'Sign in',
|
||||
'История авторизации' => 'Login history',
|
||||
'Статистика' => 'Statistics',
|
||||
'Новости' => 'News',
|
||||
'Информация' => 'Information',
|
||||
'Обратная связь' => 'Contact us',
|
||||
'Пользователи' => 'Users',
|
||||
'Автоустановка' => 'Auto install',
|
||||
'Репозиторий' => 'Repository',
|
||||
'Друзья' => 'Friends',
|
||||
'Гость' => 'Guest',
|
||||
'Смена дизайна' => 'Change theme',
|
||||
'Сменить дизайн' => 'Change theme',
|
||||
'Автообновление' => 'Auto refresh',
|
||||
'Доступные команды' => 'Available commands',
|
||||
|
||||
// ── формы и поля ─────────────────────────────────────────────────────────────
|
||||
'Введите свой E-Mail' => 'Enter your e-mail',
|
||||
'Введите свой Пароль' => 'Enter your password',
|
||||
'Пароль пользователя' => 'User password',
|
||||
'RCON Пароль' => 'RCON password',
|
||||
'Повторите пароль' => 'Repeat password',
|
||||
'Новый пароль' => 'New password',
|
||||
'Ваш новый пароль:' => 'Your new password:',
|
||||
'Пароль' => 'Password',
|
||||
'Имя пользователя' => 'Username',
|
||||
'Введите сумму' => 'Enter the amount',
|
||||
'Введите команду!' => 'Enter a command!',
|
||||
'Есть купон?' => 'Have a coupon?',
|
||||
'Название' => 'Name',
|
||||
'Статус' => 'Status',
|
||||
'Дата' => 'Date',
|
||||
'Сумма' => 'Amount',
|
||||
'Адрес' => 'Address',
|
||||
'Локация' => 'Location',
|
||||
'Клиент' => 'Client',
|
||||
'Мод' => 'Mod',
|
||||
'Задача' => 'Task',
|
||||
'Телефон:' => 'Phone:',
|
||||
'Платежная система' => 'Payment gateway',
|
||||
'Ид платежа' => 'Payment ID',
|
||||
'Оплачен до' => 'Paid until',
|
||||
'Смена E-Mail' => 'Change e-mail',
|
||||
'Секретный ключ' => 'Secret key',
|
||||
|
||||
// ── деньги, периоды, единицы ─────────────────────────────────────────────────
|
||||
'рублей' => 'RUB',
|
||||
'руб.' => 'RUB',
|
||||
'руб!' => 'RUB!',
|
||||
'руб' => 'RUB',
|
||||
'0.00 руб.' => '0.00 RUB',
|
||||
'Итого к оплате:' => 'Total due:',
|
||||
'Общая сумма пополнений' => 'Total topped up',
|
||||
'Пополнение баланса' => 'Balance top-up',
|
||||
'Пополнение баланса пользователя' => 'User balance top-up',
|
||||
'Оплата счета' => 'Invoice payment',
|
||||
'Недостаточно средств' => 'Insufficient funds',
|
||||
'МБ' => 'MB',
|
||||
'слотов.' => 'slots.',
|
||||
'слотов' => 'slots',
|
||||
'15 дней' => '15 days',
|
||||
'30 дней' => '30 days',
|
||||
'60 дней' => '60 days',
|
||||
'90 дней (-5%)' => '90 days (-5%)',
|
||||
'180 дней (-10%)' => '180 days (-10%)',
|
||||
'360 дней (-15%)' => '360 days (-15%)',
|
||||
'90 дней' => '90 days',
|
||||
'180 дней' => '180 days',
|
||||
'360 дней' => '360 days',
|
||||
'дней в неделю' => 'days a week',
|
||||
'часа в сутки' => 'hours a day',
|
||||
'Осталось %d дней' => '%d days left',
|
||||
'дней' => 'days',
|
||||
'дня' => 'days',
|
||||
'день' => 'day',
|
||||
'Одноразовая' => 'One-off',
|
||||
'Повторяющаяся' => 'Recurring',
|
||||
|
||||
// ── состояния ────────────────────────────────────────────────────────────────
|
||||
'Включена' => 'Enabled',
|
||||
'Выключена' => 'Disabled',
|
||||
'Включен' => 'Enabled',
|
||||
'Выключен' => 'Disabled',
|
||||
'В сети' => 'Online',
|
||||
'Не в сети' => 'Offline',
|
||||
'На данный момент список пуст.' => 'The list is currently empty.',
|
||||
'На данный момент у вас нет запросов.' => 'You have no requests at the moment.',
|
||||
'На данный момент у вас нет счетов.' => 'You have no invoices at the moment.',
|
||||
'На данный момент у вас нет операций со счетом.'=> 'You have no account transactions yet.',
|
||||
'У вас нет активов.' => 'You have no assets.',
|
||||
'Список логов пуст :(' => 'The log is empty :(',
|
||||
'На данный момент нет доступных локаций' => 'No locations are available at the moment',
|
||||
'К сожалению, на данный момент на хостинге нет новостей' => 'There is no hosting news at the moment',
|
||||
'Получение данных...' => 'Loading data…',
|
||||
'Ваш запрос обрабатывается, пожалуйста, подождите...' => 'Your request is being processed, please wait…',
|
||||
'Пожалуйста, подождите 10 секунд.'=> 'Please wait 10 seconds.',
|
||||
|
||||
// ── авторизация, регистрация, пароль ────────────────────────────────────────
|
||||
'Вы не авторизированы!' => 'You are not signed in!',
|
||||
'Вы не авторизированы' => 'You are not signed in',
|
||||
'Вы уже авторизированы!' => 'You are already signed in!',
|
||||
'Вы успешно авторизировались!' => 'You have signed in successfully!',
|
||||
'Вы успешно вошли!' => 'You have signed in!',
|
||||
'Вы успешно вышли из своего аккаунта' => 'You have signed out of your account',
|
||||
'Вы ввели не верный логин или пароль!' => 'Wrong e-mail or password!',
|
||||
'Вы не залогинены!' => 'You are not logged in!',
|
||||
'Нет доступа!' => 'Access denied!',
|
||||
'У вас нет доступа к данному разделу!' => 'You do not have access to this section!',
|
||||
'Авторизуйтесь для входа в панель управления' => 'Sign in to open the control panel',
|
||||
'Регистрация аккаунта' => 'Account registration',
|
||||
'Вы успешно зарегистрировались!' => 'You have registered successfully!',
|
||||
'Завершение регистрации!' => 'Registration complete!',
|
||||
'Завершение регистрации' => 'Registration complete',
|
||||
'Ваш аккаунт подтвержден!' => 'Your account is confirmed!',
|
||||
'Указанный E-Mail уже зарегистрирован!' => 'That e-mail is already registered!',
|
||||
'Указанный E-Mail уже используется!' => 'That e-mail is already in use!',
|
||||
'Пользователь с указанным E-Mail не зарегистрирован!' => 'No user is registered with that e-mail!',
|
||||
'Укажите свой реальный E-Mail!' => 'Enter a valid e-mail address!',
|
||||
'Укажите свое реальное имя!' => 'Enter your real first name!',
|
||||
'Укажите свою реальную фамилию!' => 'Enter your real last name!',
|
||||
'Введенные вами пароли не совпадают!' => 'The passwords do not match!',
|
||||
'Пароль должен содержать от 6 до 32 латинских букв, цифр и знаков <i>,.!?_-</i>!' => 'The password must be 6 to 32 Latin letters, digits or the characters <i>,.!?_-</i>!',
|
||||
'Пароль должен содержать от 8 до 32 латинских букв, цифр и знаков <i>,.!?_-</i>!' => 'The password must be 8 to 32 Latin letters, digits or the characters <i>,.!?_-</i>!',
|
||||
'Восстановление пароля' => 'Password recovery',
|
||||
'На ваш E-Mail отправлена информация для восстановления пароля!' => 'Password recovery instructions have been sent to your e-mail!',
|
||||
'Указанный ключ восстановления неверный!' => 'That recovery key is not valid!',
|
||||
'Ваш пароль успешно был изменен' => 'Your password has been changed',
|
||||
'Пароль был успешно изменен!' => 'The password has been changed!',
|
||||
'Подтвердите, что вы не робот!' => 'Please confirm that you are not a robot!',
|
||||
'Укажите правильный код с картинки! Попробуйте нажать на картинку, чтобы обновить ее.' => 'Enter the correct code from the image. Click the image to refresh it.',
|
||||
'Укажите правильный код с картинки!' => 'Enter the correct code from the image!',
|
||||
'Проверьте правильность капчи!' => 'Check the captcha and try again!',
|
||||
'Проверьте вашу почту,' => 'Check your e-mail,',
|
||||
'на нее должно прийти сообщение с дальнейшими инструкциями.' => 'a message with further instructions should arrive there.',
|
||||
'Повторите вашу попытку снова.' => 'Please try again.',
|
||||
'Через 5 секунд вы будете перенаправлены на домашнюю страницу' => 'You will be redirected to the home page in 5 seconds',
|
||||
'Изменения сохранены!' => 'Changes saved!',
|
||||
'Ошибка! Повторите действие.' => 'Error! Please try again.',
|
||||
'Неизвестная ошибка!' => 'Unknown error!',
|
||||
'Внутренняя ошибка!(Либо не привязана учётка)' => 'Internal error (or the account is not linked)',
|
||||
'Не POST запрос!' => 'Not a POST request!',
|
||||
'Не POST запрос' => 'Not a POST request',
|
||||
'Не POST данные' => 'Not POST data',
|
||||
'Упс... Что-то пошло не так.' => 'Oops… something went wrong.',
|
||||
'Вернутся на главную' => 'Back to home',
|
||||
'Ошибка!' => 'Error!',
|
||||
'Критическая ошибка' => 'Critical error',
|
||||
|
||||
// ── VK и сессии ──────────────────────────────────────────────────────────────
|
||||
'Профиль привязан!' => 'Profile linked!',
|
||||
'Профиль не привязан' => 'Profile not linked',
|
||||
'Данный профиль уже привязан к другому аккаунту!' => 'That profile is already linked to another account!',
|
||||
'Данный аккаунт уже привязан!' => 'That account is already linked!',
|
||||
'Вы успешно отвязали VK!' => 'VK has been unlinked!',
|
||||
'На данный момент у Вас не привязан VK!' => 'You have no VK account linked!',
|
||||
'Сеанс не найден!' => 'Session not found!',
|
||||
'Сеанс успешно завершён!' => 'Session ended successfully!',
|
||||
'Вы не можете завершить текущий сеанс!' => 'You cannot end your current session!',
|
||||
|
||||
// ── баланс, платежи, бонусы ──────────────────────────────────────────────────
|
||||
'Ваш баланс успешно пополнен!' => 'Your balance has been topped up!',
|
||||
'Укажите сумму пополнения в допустимом формате!' => 'Enter the top-up amount in a valid format!',
|
||||
'Укажите сумму от 10 до 5000 рублей!' => 'Enter an amount between 10 and 5000 RUB!',
|
||||
'Укажите сумму от 0 до 5000 рублей!' => 'Enter an amount between 0 and 5000 RUB!',
|
||||
'Укажите реальное число!' => 'Enter a valid number!',
|
||||
'Данная платежная система отключена!' => 'This payment gateway is disabled!',
|
||||
'Вы указали недопустимый период оплаты!' => 'You selected an invalid payment period!',
|
||||
'У вас недостаточно средств!' => 'You do not have enough funds!',
|
||||
'На Вашем счету не хватает' => 'Your account is short of',
|
||||
'На Вашем счету недостаточно средств' => 'There are not enough funds in your account',
|
||||
'Средства переведены!' => 'Funds transferred!',
|
||||
'Перевод средств пользователю ID-' => 'Funds transfer to user ID-',
|
||||
'Деньги отправлены. Вам начисленно'=> 'Money sent. You received',
|
||||
'Вы указали свой ID' => 'You entered your own ID',
|
||||
'Данного ID не сушествует!' => 'That ID does not exist!',
|
||||
'Здесь вы можете обменять свои бонусные баллы.' => 'Here you can exchange your bonus points.',
|
||||
'У вас недостаточно монет!' => 'You do not have enough coins!',
|
||||
'Бонус с реферала ID-' => 'Referral bonus, ID-',
|
||||
'Бонус за приглашенного реферала ID-' => 'Bonus for an invited referral, ID-',
|
||||
'Вы активировали скидку' => 'You activated a discount of',
|
||||
'Данного кода не существует' => 'That code does not exist',
|
||||
'Вы активирали обещаный платёж!' => 'Deferred payment activated!',
|
||||
'На данный момент Вам недоступна данная функция!' => 'This feature is not available to you right now.',
|
||||
'Внутренняя ошибка. Возможные причины:' => 'Internal error. Possible reasons:',
|
||||
'- Неверная сумма платежа.' => '- Wrong payment amount.',
|
||||
'- Неверный ID магазина.' => '- Wrong shop ID.',
|
||||
'- Не верный ID платежа.' => '- Wrong payment ID.',
|
||||
'- Данный счет уже оплачен.' => '- This invoice is already paid.',
|
||||
'- Платеж был отменен.' => '- The payment was cancelled.',
|
||||
'Если здесь нет выявленной вами причины, то обратитесь в' => 'If your case is not listed here, please contact',
|
||||
'Ошибка - Неверная сумма платежа!' => 'Error — wrong payment amount!',
|
||||
'Ошибка - Shop ID!' => 'Error — Shop ID!',
|
||||
'Ошибка - Не верный ID платежа!' => 'Error — wrong payment ID!',
|
||||
'Ошибка - Данный счет уже оплачен!' => 'Error — this invoice is already paid!',
|
||||
|
||||
// ── заказ и жизненный цикл игрового сервера ─────────────────────────────────
|
||||
'Выберите локацию' => 'Choose a location',
|
||||
'Выберите период оплаты' => 'Choose a payment period',
|
||||
'Информация о заказе' => 'Order details',
|
||||
'Игровой сервер: gs' => 'Game server: gs',
|
||||
'Сервер успешно поставлен в очередь на установку.' => 'The server has been queued for installation.',
|
||||
'Сервер успешно поставлен на переустановку!' => 'The server has been queued for reinstallation.',
|
||||
'Идет установка сервера!' => 'Server installation in progress!',
|
||||
'Идет переустановка сервера!' => 'Server reinstallation in progress!',
|
||||
'Идет создание BackUP сервера!' => 'Server backup in progress!',
|
||||
'Идет восстанновление сервера из BackUP!' => 'Server restore from backup in progress!',
|
||||
'Идет обновление сервера!' => 'Server update in progress!',
|
||||
'Сервер заблокирован!' => 'The server is suspended!',
|
||||
'Сервер должен быть выключен!' => 'The server must be stopped!',
|
||||
'Сервер должен быть вылючен!' => 'The server must be stopped!',
|
||||
'Сервер должен быть включен!' => 'The server must be running!',
|
||||
'Сервер должен быть включён!' => 'The server must be running!',
|
||||
'Запрашиваемый сервер не существует!' => 'The requested server does not exist!',
|
||||
'Вы выбрали несуществующее действие!' => 'You selected an action that does not exist!',
|
||||
'Вы не можете использовать это действие!' => 'You cannot use this action!',
|
||||
'Вы указали несуществующую локацию!' => 'You selected a location that does not exist!',
|
||||
'Вы указали несуществующую игру!' => 'You selected a game that does not exist!',
|
||||
'Данная игра не доступна для указанной локации!' => 'This game is not available in the selected location!',
|
||||
'На выбранной Вами локации нет свободных портов для данной игры' => 'The selected location has no free ports for this game',
|
||||
'Успешный запуск сервера' => 'Server started',
|
||||
'Вы успешно запустили сервер!' => 'You have started the server!',
|
||||
'Успешный перезапуск сервера' => 'Server restarted',
|
||||
'Вы успешно перезапустили сервер!'=> 'You have restarted the server!',
|
||||
'Успешное выключение сервера' => 'Server stopped',
|
||||
'Вы успешно выключили сервер!' => 'You have stopped the server!',
|
||||
'Команда успешно отправлена!' => 'Command sent!',
|
||||
'Консоль успешно очищена!' => 'Console cleared!',
|
||||
'Ошибка подключения к rcon!' => 'RCON connection failed!',
|
||||
'Возникла ошибка запроса к серверу. Код ошибки:' => 'The request to the server failed. Error code:',
|
||||
'Вы привысили ограничение размера директории на' => 'You have exceeded the directory size limit by',
|
||||
'Продление сервера' => 'Server renewal',
|
||||
'Сервер успешно продлен!' => 'The server has been renewed!',
|
||||
'Смена ядра' => 'Change core',
|
||||
'Смена билда' => 'Change build',
|
||||
'Параметры запуска' => 'Startup parameters',
|
||||
'Параметры запуска успешно изменены!' => 'Startup parameters updated!',
|
||||
'Вы успешно начали смену ядра!' => 'Core change started!',
|
||||
'Вы указали неверное ядро!' => 'That core is not valid!',
|
||||
'Вы успешно начали смену билда!' => 'Build change started!',
|
||||
'Вы указали неверный билд!' => 'That build is not valid!',
|
||||
'Вы успешно сменили карту на' => 'Map changed to',
|
||||
'Вы успешно сменили порт сервера на' => 'Server port changed to',
|
||||
'Выбранный порт не может быть использован!' => 'That port cannot be used!',
|
||||
'Выбранный порт уже занят другим сервером!' => 'That port is already taken by another server!',
|
||||
'Параметр Tickrate указан неверно!' => 'The tickrate value is not valid!',
|
||||
'Параметр FPS указан неверно!' => 'The FPS value is not valid!',
|
||||
'RCON Пароль должен содержать от 2 до 18 символов!' => 'The RCON password must be 2 to 18 characters!',
|
||||
'Пароль отправленный на сервер не соответствует нормам!' => 'The password sent to the server does not meet the requirements!',
|
||||
'Вы не указали rcon пароль в параметрах запуска сервера!' => 'You have not set an RCON password in the startup parameters!',
|
||||
'Пароль FTP успешно изменен!!' => 'FTP password changed!',
|
||||
'Пароль MySql успешно изменен!!' => 'MySQL password changed!',
|
||||
'Пароль доступа успешно изменен!' => 'Access password changed!',
|
||||
'Необходимо удалить все заблокированные IP адреса!' => 'You must remove all blocked IP addresses first!',
|
||||
'Вы не ввели IP' => 'You did not enter an IP',
|
||||
'Вы успешно заблокировали' => 'You have blocked',
|
||||
'Вы успешно разблокировали IP!' => 'You have unblocked the IP!',
|
||||
|
||||
// ── слоты, бекапы, база данных, моды ────────────────────────────────────────
|
||||
'Вы успешно установили' => 'You have set',
|
||||
'Вы указали недопустимое количество слотов!' => 'That number of slots is not allowed!',
|
||||
'Укажите количество слотов в допустимом формате!' => 'Enter the number of slots in a valid format!',
|
||||
'Невозможно установить слоты ниже заданных.' => 'Slots cannot be set below the current value.',
|
||||
'Слоты не были изменены.' => 'The slots were not changed.',
|
||||
'Увеличено до' => 'Increased to',
|
||||
'Успешное начало создания BackUP сервера.' => 'Server backup started.',
|
||||
'Вы успешно начали создание BackUP сервера!' => 'You have started a server backup!',
|
||||
'Вы успешно восстановили BackUP сервера!' => 'The server has been restored from backup!',
|
||||
'Успешное удаление BackUP сервера'=> 'Backup deleted',
|
||||
'Вы успешно удалили BackUP сервера!' => 'You have deleted the server backup!',
|
||||
'У вас нет BackUP сервера!' => 'You have no server backup!',
|
||||
'Успешное создание BackUP.' => 'Backup created.',
|
||||
'Успешное восстановление из BackUP.' => 'Restored from backup.',
|
||||
'Успешная установка сервера.' => 'Server installed successfully.',
|
||||
'Успешная переустановка сервера.' => 'Server reinstalled successfully.',
|
||||
'Успешная смена ядра.' => 'Core changed successfully.',
|
||||
'Успешная смена билда.' => 'Build changed successfully.',
|
||||
'Вы успешно создали базу данных' => 'You have created the database',
|
||||
'Вы успешно включили базу данных' => 'You have enabled the database',
|
||||
'Вы успешно выключили базу данных'=> 'You have disabled the database',
|
||||
'База данных и так включена либо вы ее еще не создали!' => 'The database is already enabled, or you have not created it yet!',
|
||||
'База данных и так выключена!' => 'The database is already disabled!',
|
||||
'База данных и так создана!' => 'The database already exists!',
|
||||
'Создайте базу MySQL и включите ее!' => 'Create a MySQL database and enable it!',
|
||||
'Данный мод не доступен для установки!' => 'This mod is not available for installation!',
|
||||
'Запрашиваемый мод не существует!'=> 'The requested mod does not exist!',
|
||||
'Установка мода' => 'Mod installation',
|
||||
'Не для Вашей игры!' => 'Not for your game!',
|
||||
'Вы успешно загрузили на сервер модуль' => 'You have uploaded the module to the server',
|
||||
'Данный файл не доступен!' => 'This file is not available!',
|
||||
'Запрашиваемый файл не существует!' => 'The requested file does not exist!',
|
||||
'Покупка файла' => 'File purchase',
|
||||
'Файл' => 'File',
|
||||
|
||||
// ── задачи планировщика ──────────────────────────────────────────────────────
|
||||
'Задача успешно создана!' => 'Task created!',
|
||||
'Задача успешно удалена!' => 'Task deleted!',
|
||||
'Задача не найдена!' => 'Task not found!',
|
||||
'Вы указали несуществующую задачу!' => 'You selected a task that does not exist!',
|
||||
'Вы не можете создать данную задачу!' => 'You cannot create this task!',
|
||||
'Данная задача уже создана!' => 'This task already exists!',
|
||||
'Вы указали несуществующий тип задачи!' => 'You selected a task type that does not exist!',
|
||||
'Данный тип задачи недоступен для задачи!' => 'This task type is not available for the task!',
|
||||
'Вы указали недопустимое время выполнения!' => 'That execution time is not allowed!',
|
||||
'Вы не можете создавать больше 4-х задач!' => 'You cannot create more than 4 tasks!',
|
||||
'[Планировщик] Успешный запуск сервера' => '[Scheduler] Server started',
|
||||
'[Планировщик] Успешное выключение сервера' => '[Scheduler] Server stopped',
|
||||
'[Планировщик] Успешный перезапуск сервера' => '[Scheduler] Server restarted',
|
||||
'Сервер был восcтановлен после падения' => 'The server was recovered after a crash',
|
||||
|
||||
// ── друзья / совладельцы сервера ─────────────────────────────────────────────
|
||||
'Вы успешно добавили друга!' => 'Friend added!',
|
||||
'Вы успешно удалили друга!' => 'Friend removed!',
|
||||
'Вы не являетесь владельцем сервера. У вас нет доступа к разделу!' => 'You are not the server owner, so you cannot access this section!',
|
||||
'Вы указали недоступный ID пользователя!' => 'That user ID is not available!',
|
||||
'Запрашиваемый пользователь не существует!' => 'The requested user does not exist!',
|
||||
'Запрашиваемый пользователь уже добавлен!' => 'That user has already been added!',
|
||||
'Запрашиваемый пользователь является владельцем сервера!' => 'That user is the server owner!',
|
||||
|
||||
// ── тестовый период ──────────────────────────────────────────────────────────
|
||||
'Вы уже брали тестовый период, либо у вас нет одобрения администратора!' => 'You have already used the trial period, or an administrator has not approved it!',
|
||||
'Тестовый период отключен на хостинге!' => 'The trial period is disabled on this hosting!',
|
||||
|
||||
// ── тикеты ───────────────────────────────────────────────────────────────────
|
||||
'Вы успешно создали запрос!' => 'Your request has been created!',
|
||||
'Вы успешно закрыли запрос!' => 'You have closed the request!',
|
||||
'Вы успешно отправили сообщение!' => 'Your message has been sent!',
|
||||
'Данный запрос закрыт, оставить новое сообщение невозможно!' => 'This request is closed, you cannot add a new message!',
|
||||
'Запрашиваемый запрос не существует!' => 'The requested ticket does not exist!',
|
||||
'Название тикета должно содержать от 6 до 32 символов!' => 'The ticket subject must be 6 to 32 characters!',
|
||||
'Текст тикета должен содержать от 10 до 350 символов!' => 'The ticket text must be 10 to 350 characters!',
|
||||
'Текст сообщения должен содержать от 1 до 350 символов.' => 'The message text must be 1 to 350 characters.',
|
||||
'Нельзя создать тикет, так как за сегодня вы создали больше 3 тикетов.' => 'You cannot create a ticket: you have already created more than 3 today.',
|
||||
'Новые запросы' => 'New requests',
|
||||
|
||||
// ── загрузка изображений ─────────────────────────────────────────────────────
|
||||
'Можно загружать только изображения в форматах jpg, jpeg и png' => 'Only jpg, jpeg and png images can be uploaded',
|
||||
'Изображение не загружено!' => 'The image was not uploaded!',
|
||||
'Аватар успешно был загружен!' => 'Avatar uploaded!',
|
||||
'Загружаемое изображение превышает допустимые нормы!' => 'The uploaded image is too large!',
|
||||
'Размер изображения не должено превышать 512Кб' => 'The image must not exceed 512 KB',
|
||||
'Размер изображения не должено превышать 5120Кб' => 'The image must not exceed 5120 KB',
|
||||
'Изображение:' => 'Image:',
|
||||
|
||||
// ── новости ──────────────────────────────────────────────────────────────────
|
||||
'Вы успешно создали коментарий!' => 'Your comment has been posted!',
|
||||
'Запрашиваемая новость не существует!' => 'The requested news item does not exist!',
|
||||
'Текст коментариф должен содержать от 10 до 128 символов.' => 'The comment must be 10 to 128 characters.',
|
||||
'Новости и статусы на REDL.IO' => 'News and status on REDL.IO',
|
||||
|
||||
// ── веб-хостинг ──────────────────────────────────────────────────────────────
|
||||
'Веб-хостинг №' => 'Web hosting no.',
|
||||
'успешно заказан.' => 'ordered successfully.',
|
||||
'Веб.хостинг не доступен для заказа!' => 'Web hosting is not available for ordering!',
|
||||
'Вы указали несуществующий тариф!'=> 'You selected a plan that does not exist!',
|
||||
'Данный тариф не доступен для указанной локации!' => 'This plan is not available in the selected location!',
|
||||
'Веб-хостинг заблокирован!' => 'Web hosting is suspended!',
|
||||
'Запрашиваемый веб-хостинг не существует!' => 'The requested web hosting does not exist!',
|
||||
|
||||
// ── темы оформления ──────────────────────────────────────────────────────────
|
||||
'Тема установлена!' => 'Theme applied!',
|
||||
'Тема Default успешно установлена!' => 'The Default theme has been applied!',
|
||||
'Тема Material успешно установлена!' => 'The Material theme has been applied!',
|
||||
'Тема Material gray успешно установлена!'=> 'The Material gray theme has been applied!',
|
||||
'Тема Material light успешно установлена!'=> 'The Material light theme has been applied!',
|
||||
'Тема Default' => 'Default theme',
|
||||
|
||||
// ── главная страница и маркетинг ─────────────────────────────────────────────
|
||||
'Удобная панель управления' => 'A convenient control panel',
|
||||
'С нашей панелью управления, управлять сервером легко и просто с ней справится как опытный пользователь так и новичок.' => 'Our control panel makes managing a server simple — both for experienced users and for beginners.',
|
||||
'Преимущества нашего хостинга' => 'Why choose our hosting',
|
||||
'Мощные процессоры Intel' => 'Powerful Intel processors',
|
||||
'Защита от DDoS атак на оборудование.' => 'Hardware-level DDoS protection.',
|
||||
'Полный доступ к FTP' => 'Full FTP access',
|
||||
'Создание резервных копий backup' => 'Backup creation',
|
||||
'Скоростные SSD диски' => 'Fast SSD storage',
|
||||
'Хостинг с искусственным интеллектом' => 'AI-powered hosting',
|
||||
'С уважением, Администрация REDL.IO!' => 'Sincerely, the REDL.IO team!',
|
||||
|
||||
// ── месяцы (графики и даты) ─────────────────────────────────────────────────
|
||||
'Январь' => 'January', 'Февраль' => 'February', 'Март' => 'March',
|
||||
'Апрель' => 'April', 'Май' => 'May', 'Июнь' => 'June',
|
||||
'Июль' => 'July', 'Август' => 'August', 'Сентябрь' => 'September',
|
||||
'Октябрь'=> 'October', 'Ноябрь' => 'November', 'Декабрь' => 'December',
|
||||
'Янв.' => 'Jan', 'Фев.' => 'Feb', 'Март.' => 'Mar', 'Апр.' => 'Apr',
|
||||
'Май.' => 'May', 'Июнь.' => 'Jun', 'Июль.' => 'Jul', 'Авг.' => 'Aug',
|
||||
'Сент.'=> 'Sep', 'Окт.' => 'Oct', 'Ноя.' => 'Nov', 'Дек.' => 'Dec',
|
||||
|
||||
// ── элементы графиков ────────────────────────────────────────────────────────
|
||||
'Скачать PNG' => 'Download PNG',
|
||||
'Скачать CSV' => 'Download CSV',
|
||||
'Скачать SVG' => 'Download SVG',
|
||||
'Выбор Zoom' => 'Zoom selection',
|
||||
'Увеличить' => 'Zoom in',
|
||||
'Уменьшить' => 'Zoom out',
|
||||
'Панорамирование'=> 'Panning',
|
||||
'Сброс Масштаба' => 'Reset zoom',
|
||||
|
||||
// ── служебные ошибки движка ──────────────────────────────────────────────────
|
||||
'Ошибка: Не удалось загрузить контроллер' => 'Error: could not load the controller',
|
||||
'Ошибка: Не удалось загрузить шаблон' => 'Error: could not load the template',
|
||||
'Ошибка: Не удалось загрузить модель' => 'Error: could not load the model',
|
||||
'Ошибка: Не удалось загрузить библиотеку' => 'Error: could not load the library',
|
||||
'Ошибка: Не удалось загрузить драйвер базы данных' => 'Error: could not load the database driver',
|
||||
'Ошибка: Не удалось загрузить файл конфигурации игр!' => 'Error: could not load the games configuration file!',
|
||||
'Ошибка: Не удалось загрузить файл конфигурации!' => 'Error: could not load the configuration file!',
|
||||
'Укажите допустимый статус!' => 'Choose a valid status!',
|
||||
|
||||
// ── админка: часто встречающиеся подписи ────────────────────────────────────
|
||||
'Введите название локации' => 'Enter the location name',
|
||||
'Название локации должно содержать от 2 до 32 символов!' => 'The location name must be 2 to 32 characters!',
|
||||
'Введите ID' => 'Enter the ID',
|
||||
'Введите имя пользователя' => 'Enter the username',
|
||||
'Имя пользователя должно содержать от 2 до 32 символов!' => 'The username must be 2 to 32 characters!',
|
||||
'Укажите допустимый IP!' => 'Enter a valid IP!',
|
||||
'Укажите допустимую стоимость!' => 'Enter a valid price!',
|
||||
'Описание должно содержать от 2 до 500 символов!' => 'The description must be 2 to 500 characters!',
|
||||
'Введите название игры' => 'Enter the game name',
|
||||
'Название игры должно содержать от 2 до 92 символов!' => 'The game name must be 2 to 92 characters!',
|
||||
'Введите код игры' => 'Enter the game code',
|
||||
'Код игры должен содержать от 2 до 8 символов!' => 'The game code must be 2 to 8 characters!',
|
||||
'Название категории должно содержать от 2 до 32 символов!' => 'The category name must be 2 to 32 characters!',
|
||||
'Введите название тарифа' => 'Enter the plan name',
|
||||
'Введите стоимость тарифа' => 'Enter the plan price',
|
||||
'Введите стоимость за один слот' => 'Enter the price per slot',
|
||||
'Введите минимальное количество слотов для заказа' => 'Enter the minimum number of slots per order',
|
||||
'Введите максимальное количество слотов для заказа' => 'Enter the maximum number of slots per order',
|
||||
'Введите минимальный порт для заказа' => 'Enter the lowest port available for orders',
|
||||
'Введите количество ядер на один сервер' => 'Enter the number of cores per server',
|
||||
'Введите объем оперативной памяти на один сервер (в МБ)' => 'Enter the amount of RAM per server (MB)',
|
||||
'Введите объем SSD на один сервер (в МБ)' => 'Enter the SSD size per server (MB)',
|
||||
'Укажите допустимое количество ядер!' => 'Enter a valid number of cores!',
|
||||
'Укажите допустимый объем оперативной памяти!' => 'Enter a valid amount of RAM!',
|
||||
'Укажите допустимую объем жесткого диска!' => 'Enter a valid disk size!',
|
||||
'Укажите допустимый Query-драйвер.' => 'Choose a valid query driver.',
|
||||
'Введите IP (Который видит пользователь)' => 'Enter the IP shown to the user',
|
||||
'Введите IP (По которому подключаются)' => 'Enter the IP used for connections',
|
||||
'Введите IP (0.0.0.0)' => 'Enter the IP (0.0.0.0)',
|
||||
'Введите URL (domain.ru)' => 'Enter the URL (domain.com)',
|
||||
'Введите NS Сервера локации (ns1.domain.ru и ns2.domain.ru)' => 'Enter the location name servers (ns1.domain.com and ns2.domain.com)',
|
||||
'Введите название шаблона в панели ISP' => 'Enter the template name in the ISP panel',
|
||||
|
||||
// ── заголовки и меню админки ─────────────────────────────────────────────────
|
||||
'Панель управления' => 'Control panel',
|
||||
'Панель Администратора' => 'Administrator panel',
|
||||
'Администратора' => 'Administrator',
|
||||
'Администратор' => 'Administrator',
|
||||
'Меню управления' => 'Management menu',
|
||||
'Меню' => 'Menu',
|
||||
'На главную' => 'Home',
|
||||
'Контакты' => 'Contacts',
|
||||
'Пользователей на сайте:' => 'Users online:',
|
||||
'Система' => 'System',
|
||||
'Настройки' => 'Settings',
|
||||
'Игровые сервера' => 'Game servers',
|
||||
'Игровых серверов' => 'Game servers',
|
||||
'Доступных игр' => 'Games available',
|
||||
'Пользователей' => 'Users',
|
||||
'Пользователь' => 'User',
|
||||
'Список пользователей' => 'User list',
|
||||
'Все запросы' => 'All requests',
|
||||
'Все категории' => 'All categories',
|
||||
'Создать категорию' => 'New category',
|
||||
'Написать клиенту' => 'Message the client',
|
||||
'Платежи' => 'Payments',
|
||||
'WEB сайты' => 'Websites',
|
||||
'Рассылка E-mail' => 'E-mail campaign',
|
||||
'Операции пользователей' => 'User transactions',
|
||||
'Список новостей' => 'News list',
|
||||
'Добавить новость' => 'Add news',
|
||||
'Промо коды' => 'Promo codes',
|
||||
'Список промо кодов' => 'Promo code list',
|
||||
'Добавить промо код' => 'Add promo code',
|
||||
'Промо' => 'Promo',
|
||||
'Игры' => 'Games',
|
||||
'Список игр' => 'Game list',
|
||||
'Добавить игру' => 'Add game',
|
||||
'Game локации' => 'Game locations',
|
||||
'Список локаций' => 'Location list',
|
||||
'Добавить локацию' => 'Add location',
|
||||
'WEB Тарифы' => 'Web plans',
|
||||
'WEB Локации' => 'Web locations',
|
||||
'Дата регистрации' => 'Registration date',
|
||||
'Статус активации' => 'Activation status',
|
||||
'Информация и бонусы' => 'Information and bonuses',
|
||||
'Статистика хостинга' => 'Hosting statistics',
|
||||
|
||||
// ── пустые состояния админки ─────────────────────────────────────────────────
|
||||
'На данный момент у вас нет серверов.' => 'You have no servers yet.',
|
||||
'На данный момент у вас нет веб-хостингов.' => 'You have no web hosting yet.',
|
||||
'На данный момент нет игровых серверов.' => 'There are no game servers yet.',
|
||||
'На данный момент нет локаций.' => 'There are no locations yet.',
|
||||
'На данный момент нет операций.' => 'There are no transactions yet.',
|
||||
'На данный момент в тех.поддержку нет запросов.' => 'There are no support requests at the moment.',
|
||||
'Заблокировано:' => 'Suspended:',
|
||||
'Всего:' => 'Total:',
|
||||
'Новых запросов:' => 'New requests:',
|
||||
'Запросов:' => 'Requests:',
|
||||
'Онлайн:' => 'Online:',
|
||||
'Серверов:' => 'Servers:',
|
||||
'Последний счет:' => 'Last invoice:',
|
||||
'Заработано:' => 'Earned:',
|
||||
'Обновлено в' => 'Updated at',
|
||||
'Активен' => 'Active',
|
||||
'Подтвержден' => 'Confirmed',
|
||||
'Установлен' => 'Installed',
|
||||
'Код' => 'Code',
|
||||
'Слоты' => 'Slots',
|
||||
'Порты' => 'Ports',
|
||||
'Цена за слот' => 'Price per slot',
|
||||
|
||||
// ── настройки: вкладки и подписи ─────────────────────────────────────────────
|
||||
'Общие настройки' => 'General settings',
|
||||
'Платежные системы' => 'Payment gateways',
|
||||
'Прочие настройки' => 'Other settings',
|
||||
'Применить настройки' => 'Apply settings',
|
||||
'URL сайта' => 'Site URL',
|
||||
'Название сайта' => 'Site name',
|
||||
'Описание сайта' => 'Site description',
|
||||
'Ключевые слова' => 'Keywords',
|
||||
'Имя отправителя' => 'Sender name',
|
||||
'E-mail Службы поддержки' => 'Support e-mail',
|
||||
'ID Вашей группы ВКонтакте' => 'Your VK group ID',
|
||||
'Cсылка на ваш логотип' => 'Logo URL',
|
||||
'Введите URL сайта' => 'Enter the site URL',
|
||||
'Введите название сайта' => 'Enter the site name',
|
||||
'Введите описание сайта' => 'Enter the site description',
|
||||
'Введите ключевые слова' => 'Enter the keywords',
|
||||
'Введите имя отправителя' => 'Enter the sender name',
|
||||
'Введите E-mail Службы поддержки' => 'Enter the support e-mail',
|
||||
'Введите ID Вашей группы ВКонтакте' => 'Enter your VK group ID',
|
||||
'Введите ссылку на ваш логотип' => 'Enter the logo URL',
|
||||
'Введите секретный ключ' => 'Enter the secret key',
|
||||
'Введите секретный ключ №1' => 'Enter secret key no. 1',
|
||||
'Введите секретный ключ №2' => 'Enter secret key no. 2',
|
||||
'Введите процент' => 'Enter the percentage',
|
||||
'Введите номер' => 'Enter the number',
|
||||
'Введите URL' => 'Enter the URL',
|
||||
'Введите Token' => 'Enter the token',
|
||||
'Введите тему' => 'Enter the subject',
|
||||
'Сообщение...' => 'Message…',
|
||||
'Веб.хостинг' => 'Web hosting',
|
||||
'Модуль быстрой оплаты' => 'Quick payment module',
|
||||
'Платежная система быстрой оплаты'=> 'Quick payment gateway',
|
||||
'Яндекс деньги' => 'YooMoney',
|
||||
'Номер счета' => 'Account number',
|
||||
'Секретный пароль' => 'Secret password',
|
||||
'Дополнительный ключ' => 'Additional key',
|
||||
'ID проекта' => 'Project ID',
|
||||
'Unitpay секретный ключ' => 'Unitpay secret key',
|
||||
'Токен' => 'Token',
|
||||
'Публичный ключ' => 'Public key',
|
||||
'Введите публичный ключ' => 'Enter the public key',
|
||||
'Настройки формы Qiwi' => 'Qiwi form settings',
|
||||
'Включены' => 'Enabled',
|
||||
'Выключены' => 'Disabled',
|
||||
'Код темы (themeCode)' => 'Theme code (themeCode)',
|
||||
'Введите код темы (themeCode)' => 'Enter the theme code (themeCode)',
|
||||
'Тестовый период' => 'Trial period',
|
||||
'Заказ запрещен' => 'Ordering disabled',
|
||||
'Заказ разрешен (по одобрению)' => 'Ordering allowed (with approval)',
|
||||
'Подтверждение E-mail' => 'E-mail confirmation',
|
||||
'Тех.работы' => 'Maintenance',
|
||||
'Хостинг доступен' => 'Hosting available',
|
||||
'Хостинг закрыт на тех.работы' => 'Hosting closed for maintenance',
|
||||
'Статус тех.работ' => 'Maintenance status',
|
||||
'Сообщение тех.работ' => 'Maintenance message',
|
||||
'Введите сообщение тех.работ' => 'Enter the maintenance message',
|
||||
'Хостинг временно закрыт на обслуживание!' => 'The hosting is temporarily closed for maintenance!',
|
||||
'ID приложения' => 'Application ID',
|
||||
'Введите ID приложения' => 'Enter the application ID',
|
||||
'Статус авторизаци' => 'Login status',
|
||||
'Статус бота' => 'Bot status',
|
||||
'Стоимость смены порта' => 'Port change price',
|
||||
'Введите стоимость смены порта' => 'Enter the port change price',
|
||||
'Конвертация бонусов' => 'Bonus conversion',
|
||||
'Процент реферальной системы (за заказ услуг)' => 'Referral percentage (on service orders)',
|
||||
'Процент бонусов за пополнение' => 'Bonus percentage on top-ups',
|
||||
'Контактый номер' => 'Contact number',
|
||||
'Введите минимальный бонус' => 'Enter the minimum bonus',
|
||||
'Введите максимальный бонус' => 'Enter the maximum bonus',
|
||||
'Введите ID пользователя' => 'Enter the user ID',
|
||||
'Введите login' => 'Enter the login',
|
||||
'Введите pass' => 'Enter the password',
|
||||
|
||||
// ── капча в настройках ───────────────────────────────────────────────────────
|
||||
'Защита от ботов (reCAPTCHA v2)' => 'Bot protection (reCAPTCHA v2)',
|
||||
'Выключена (капчи нет на сайте)' => 'Disabled (no captcha on the site)',
|
||||
'Включена (нужны ключи ниже)' => 'Enabled (requires the keys below)',
|
||||
'Капча при входе, регистрации, восстановлении пароля и в тикетах. Включайте только после того, как вписали оба ключа.' => 'Captcha on login, registration, password recovery and tickets. Only enable it after both keys are filled in.',
|
||||
'Site key (публичный ключ) — из панели google.com/recaptcha, тип reCAPTCHA v2 «Я не робот»' => 'Site key (public key) — from google.com/recaptcha, reCAPTCHA v2 "I am not a robot"',
|
||||
'Secret key (секретный ключ) того же сайта' => 'Secret key for the same site',
|
||||
|
||||
// ── акции ────────────────────────────────────────────────────────────────────
|
||||
'Проведение акции' => 'Run a promotion',
|
||||
'Провести акцию' => 'Run promotion',
|
||||
'Начать акцию' => 'Start promotion',
|
||||
'Раздача реальных денег' => 'Give away real money',
|
||||
'Выдать только 1 пользователю' => 'Give to one user only',
|
||||
'Создавать новость об проведённом розыгрыше' => 'Create a news post about the giveaway',
|
||||
|
||||
// ── проверка системы ─────────────────────────────────────────────────────────
|
||||
'Функция' => 'Feature',
|
||||
'Установка' => 'Installation',
|
||||
'Работоспособность бд для сервера.'=> 'Database support for servers.',
|
||||
'Подключение по ssh.' => 'SSH connectivity.',
|
||||
'Просмотр каптчи.' => 'Captcha rendering.',
|
||||
'Кэширование памяти.' => 'Memory caching.',
|
||||
'Время' => 'Time',
|
||||
'Ссылка для браузера' => 'Browser link',
|
||||
'1 раз в 00:00' => 'daily at 00:00',
|
||||
'1 раз в минуту' => 'every minute',
|
||||
'1 раз в 10 минут' => 'every 10 minutes',
|
||||
'1 раз в 30 минут' => 'every 30 minutes',
|
||||
'1 раз в час' => 'hourly',
|
||||
'1 раз в неделю' => 'weekly',
|
||||
|
||||
// ── прочее, найденное при проверке страниц ──────────────────────────────────
|
||||
'У вас ещё нет аккаунта?' => 'Do not have an account yet?',
|
||||
'Забыли пароль?' => 'Forgot your password?',
|
||||
'Восстановить пароль' => 'Reset password',
|
||||
'Введите Ваше Имя' => 'Enter your first name',
|
||||
'Введите Вашу Фамилию' => 'Enter your last name',
|
||||
'Введите Ваш E-Mail' => 'Enter your e-mail',
|
||||
'Введите Пароль' => 'Enter a password',
|
||||
'Повторите Пароль' => 'Repeat the password',
|
||||
'Без приглашения' => 'No invitation',
|
||||
'Введите ваше имя..' => 'Enter your first name…',
|
||||
'Введите вашу фамилию..' => 'Enter your last name…',
|
||||
'Введите ваш email..' => 'Enter your e-mail…',
|
||||
'Вопрос' => 'Question',
|
||||
'Проблемы с сервером' => 'Problems with a server',
|
||||
'Проблемы с аккаунтом' => 'Problems with an account',
|
||||
'Сотрудничество' => 'Partnership',
|
||||
'Наша техническая поддержка работает' => 'Our technical support works',
|
||||
'дней в году. Мы готовы помочь вам в любое время суток.' => 'days a year. We are ready to help you at any hour.',
|
||||
|
||||
// ── найдено на второй проверке страниц ──────────────────────────────────────
|
||||
'Панель' => 'Panel',
|
||||
'Вход в аккаунт' => 'Account login',
|
||||
'Попытка входа в аккаунт' => 'Account login attempt',
|
||||
'в' => 'at',
|
||||
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/*
|
||||
Русский — исходный язык шаблонов панели, поэтому словарь ему не нужен:
|
||||
текст уже написан по-русски прямо в вёрстке, и Lang для кода 'ru' вообще
|
||||
не загружает переводы (см. engine/main/lang.php).
|
||||
|
||||
Файл оставлен как точка расширения: если однажды исходные строки заменят
|
||||
на нейтральные ключи, русские переводы лягут сюда.
|
||||
|
||||
Как добавить свой язык (например немецкий):
|
||||
1. Скопируйте application/lang/en.php в application/lang/de.php
|
||||
2. Переведите значения
|
||||
3. Добавьте код в Lang::AVAILABLE в engine/main/lang.php
|
||||
|
||||
Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом.
|
||||
*/
|
||||
|
||||
return array();
|
||||
@@ -189,6 +189,17 @@ Developed by Samir Shelenko and Alexander Zemlyanoy (https://vk.com/id00v / htt
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar">
|
||||
<?php $__lang = Lang::instance(); if($__lang): ?>
|
||||
<div class="topbar-item mr-1 mr-lg-3">
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="Language">
|
||||
<?php foreach($__lang->available() as $__c): ?>
|
||||
<a href="<?php echo htmlspecialchars($__lang->switchUrl($__c), ENT_QUOTES, 'UTF-8') ?>"
|
||||
class="btn btn-sm font-weight-bolder text-uppercase <?php echo $__c === $__lang->code() ? 'btn-primary' : 'btn-light-primary' ?>"
|
||||
title="<?php echo htmlspecialchars($__lang->name($__c), ENT_QUOTES, 'UTF-8') ?>"><?php echo $__c ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="topbar-item mr-1 mr-lg-3">
|
||||
<a <?if($oplata_status == 1):?> data-toggle="modal" data-target="#hostin" <? else:?>onClick="redirect('/account/pay')"<?endif;?> class="btn btn-fixed-height btn-light-primary font-weight-bolder font-size-sm px-5 my-1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="svg-icon svg-icon-md">
|
||||
|
||||
@@ -189,6 +189,17 @@ Developed by Samir Shelenko and Alexander Zemlyanoy (https://vk.com/id00v / htt
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar">
|
||||
<?php $__lang = Lang::instance(); if($__lang): ?>
|
||||
<div class="topbar-item mr-1 mr-lg-3">
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="Language">
|
||||
<?php foreach($__lang->available() as $__c): ?>
|
||||
<a href="<?php echo htmlspecialchars($__lang->switchUrl($__c), ENT_QUOTES, 'UTF-8') ?>"
|
||||
class="btn btn-sm font-weight-bolder text-uppercase <?php echo $__c === $__lang->code() ? 'btn-primary' : 'btn-light-primary' ?>"
|
||||
title="<?php echo htmlspecialchars($__lang->name($__c), ENT_QUOTES, 'UTF-8') ?>"><?php echo $__c ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="topbar-item mr-1 mr-lg-3">
|
||||
<a <?if($oplata_status == 1):?> data-toggle="modal" data-target="#hostin" <? else:?>onClick="redirect('/account/pay')"<?endif;?> class="btn btn-fixed-height btn-light-primary font-weight-bolder font-size-sm px-5 my-1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="svg-icon svg-icon-md">
|
||||
|
||||
@@ -133,6 +133,11 @@ Developed by Samir Shelenko and Alexander Zemlyanoy (https://vk.com/id00v / htt
|
||||
<span class="text-muted font-weight-bold mr-2">2026©</span>
|
||||
<a href="https://redl.io" target="_blank" class="text-dark-50 text-hover-primary"><?php echo $description ?></a>
|
||||
</div>
|
||||
<div class="font-size-h6 font-weight-bolder order-3 py-2 py-md-0 mr-md-4">
|
||||
<?php $__lang = Lang::instance(); if($__lang): ?>
|
||||
<?php foreach($__lang->available() as $__i => $__c): ?><?php if($__i): ?><span class="text-muted mx-1">·</span><?php endif; ?><a href="<?php echo htmlspecialchars($__lang->switchUrl($__c), ENT_QUOTES, 'UTF-8') ?>" class="<?php echo $__c === $__lang->code() ? 'text-primary' : 'text-dark-50 text-hover-primary' ?>"><?php echo htmlspecialchars($__lang->name($__c), ENT_QUOTES, 'UTF-8') ?></a><?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="font-size-h5 font-weight-bolder order-1 order-md-2 py-2 py-md-0">
|
||||
<a href="javascript:;" data-toggle="modal" data-target="#hostin_supports" class="text-primary">Обратная связь</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/*
|
||||
Многоязычность панели. Добавлено в сборке REDL.
|
||||
Поддерживается и развивается с помощью REDL.IO — Хостинг с искусственным интеллектом.
|
||||
|
||||
Как работает
|
||||
------------
|
||||
Панель изначально написана с русским текстом прямо в шаблонах (около 1500 строк
|
||||
в 200 файлах). Переписывать их все на вызовы t() — большой риск что-нибудь сломать,
|
||||
поэтому перевод делается на уровне ГОТОВОГО ОТВЕТА: index.php оборачивает вывод в
|
||||
ob_start(), и перед отправкой браузеру строки заменяются по словарю.
|
||||
|
||||
Плюсы такого подхода:
|
||||
* работает сразу во всей панели, включая админку и письма;
|
||||
* ничего не может «исчезнуть» — если фразы нет в словаре, остаётся русский текст;
|
||||
* добавить язык = положить один файл в application/lang/.
|
||||
|
||||
Порядок определения языка:
|
||||
1. ?lang=en — явный выбор пользователя, запоминается в cookie на год
|
||||
2. cookie lang — прошлый выбор
|
||||
3. Accept-Language браузера — автоопределение
|
||||
4. 'lang' из application/config.php, иначе русский
|
||||
*/
|
||||
|
||||
class Lang {
|
||||
|
||||
/** Языки, для которых есть словарь. */
|
||||
const AVAILABLE = array('ru', 'en');
|
||||
|
||||
/** Языки, для которых уместнее оставить русский интерфейс. */
|
||||
private static $ruFamily = array('ru','uk','be','kk','ky','uz','az','hy','ka','tg','tk','mo','md');
|
||||
|
||||
private $code = 'ru';
|
||||
private $map = array();
|
||||
private $patterns = null;
|
||||
private static $instance = null;
|
||||
|
||||
public function __construct($registry = null) {
|
||||
$this->code = $this->detect($registry);
|
||||
|
||||
// Русский — исходный язык шаблонов, словарь ему не нужен.
|
||||
if ($this->code !== 'ru') {
|
||||
$file = APPLICATION_DIR . 'lang/' . $this->code . '.php';
|
||||
if (is_readable($file)) {
|
||||
$map = include($file);
|
||||
if (is_array($map)) {
|
||||
// пустые переводы отбрасываем, иначе текст пропал бы со страницы
|
||||
foreach ($map as $from => $to) {
|
||||
if (is_string($from) && is_string($to) && $from !== '' && $to !== '') {
|
||||
$this->map[$from] = $to;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self::$instance = $this;
|
||||
}
|
||||
|
||||
public static function instance() {
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function code() {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function available() {
|
||||
return self::AVAILABLE;
|
||||
}
|
||||
|
||||
/** Человеческое имя языка для переключателя. */
|
||||
public function name($code) {
|
||||
$names = array('ru' => 'Русский', 'en' => 'English');
|
||||
return isset($names[$code]) ? $names[$code] : strtoupper($code);
|
||||
}
|
||||
|
||||
/** Адрес текущей страницы с другим языком — для ссылок переключателя. */
|
||||
public function switchUrl($code) {
|
||||
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
|
||||
$parts = explode('#', $uri, 2);
|
||||
$uri = $parts[0];
|
||||
$parts = explode('?', $uri, 2);
|
||||
$path = $parts[0];
|
||||
$query = array();
|
||||
if (isset($parts[1]) && $parts[1] !== '') {
|
||||
parse_str($parts[1], $query);
|
||||
}
|
||||
$query['lang'] = $code;
|
||||
return $path . '?' . http_build_query($query);
|
||||
}
|
||||
|
||||
private function detect($registry) {
|
||||
// 1. явный выбор
|
||||
if (isset($_GET['lang']) && in_array($_GET['lang'], self::AVAILABLE, true)) {
|
||||
$code = $_GET['lang'];
|
||||
if (!headers_sent()) {
|
||||
setcookie('lang', $code, time() + 31536000, '/');
|
||||
}
|
||||
$_COOKIE['lang'] = $code;
|
||||
return $code;
|
||||
}
|
||||
|
||||
// 2. прошлый выбор
|
||||
if (isset($_COOKIE['lang']) && in_array($_COOKIE['lang'], self::AVAILABLE, true)) {
|
||||
return $_COOKIE['lang'];
|
||||
}
|
||||
|
||||
// 3. автоопределение по браузеру
|
||||
if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
foreach (self::parseAccept($_SERVER['HTTP_ACCEPT_LANGUAGE']) as $tag) {
|
||||
$short = strtolower(substr($tag, 0, 2));
|
||||
if (in_array($short, self::$ruFamily, true)) {
|
||||
return 'ru';
|
||||
}
|
||||
if (in_array($short, self::AVAILABLE, true)) {
|
||||
return $short;
|
||||
}
|
||||
}
|
||||
// браузер просит язык, которого у нас нет и который не русскоязычный
|
||||
return 'en';
|
||||
}
|
||||
|
||||
// 4. значение по умолчанию из конфигурации
|
||||
if ($registry && isset($registry->config)) {
|
||||
$default = $registry->config->lang;
|
||||
if ($default && in_array($default, self::AVAILABLE, true)) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
return 'ru';
|
||||
}
|
||||
|
||||
/** Разбор Accept-Language в список тегов по убыванию веса q. */
|
||||
private static function parseAccept($header) {
|
||||
$tags = array();
|
||||
foreach (explode(',', $header) as $chunk) {
|
||||
$chunk = trim($chunk);
|
||||
if ($chunk === '') { continue; }
|
||||
$q = 1.0;
|
||||
if (strpos($chunk, ';') !== false) {
|
||||
list($chunk, $params) = explode(';', $chunk, 2);
|
||||
if (preg_match('~q\s*=\s*([0-9.]+)~i', $params, $m)) {
|
||||
$q = (float)$m[1];
|
||||
}
|
||||
$chunk = trim($chunk);
|
||||
}
|
||||
if ($chunk !== '' && $chunk !== '*') {
|
||||
$tags[] = array($chunk, $q);
|
||||
}
|
||||
}
|
||||
usort($tags, function($a, $b) {
|
||||
if ($a[1] == $b[1]) { return 0; }
|
||||
return ($a[1] < $b[1]) ? 1 : -1;
|
||||
});
|
||||
$out = array();
|
||||
foreach ($tags as $t) { $out[] = $t[0]; }
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Перевод одной строки. Нет в словаре — возвращаем как есть. */
|
||||
public function get($text) {
|
||||
return isset($this->map[$text]) ? $this->map[$text] : $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Коллбэк для ob_start: переводит весь ответ перед отправкой.
|
||||
* Ответы AJAX обрабатываются отдельно: json_encode экранирует кириллицу
|
||||
* в \uXXXX, и словарь по сырому UTF-8 туда бы не попал.
|
||||
*/
|
||||
public function apply($buffer) {
|
||||
if ($this->code === 'ru' || !$this->map || !is_string($buffer) || $buffer === '') {
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
$head = ltrim($buffer);
|
||||
if ($head !== '' && ($head[0] === '{' || $head[0] === '[')) {
|
||||
$data = json_decode($head, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
|
||||
$json = json_encode($this->walk($data), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($json !== false) {
|
||||
return $json;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->replace($buffer);
|
||||
}
|
||||
|
||||
private function walk($value) {
|
||||
if (is_array($value)) {
|
||||
$out = array();
|
||||
foreach ($value as $k => $v) { $out[$k] = $this->walk($v); }
|
||||
return $out;
|
||||
}
|
||||
return is_string($value) ? $this->replace($value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Замена по словарю ТОЛЬКО на границах слов.
|
||||
*
|
||||
* Простой strtr() здесь не годится: короткий ключ подменяется внутри более
|
||||
* длинного слова и получается каша из двух языков — 'Мод' превращал
|
||||
* «Модуль» в «Modуль», а 'день' делал из «деньги» «dayги». Поэтому вокруг
|
||||
* каждого ключа требуем отсутствие буквы. Цифра слева разрешена намеренно:
|
||||
* иначе «0руб.» осталось бы без перевода.
|
||||
*
|
||||
* Ключи отсортированы от длинных к коротким, чтобы «Сменить пароль»
|
||||
* срабатывало раньше, чем «Пароль».
|
||||
*/
|
||||
private function replace($text) {
|
||||
if ($this->patterns === null) {
|
||||
$this->buildPatterns();
|
||||
}
|
||||
foreach ($this->patterns as $pattern) {
|
||||
$out = preg_replace_callback($pattern, array($this, 'hit'), $text);
|
||||
if ($out !== null) { $text = $out; }
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function hit($m) {
|
||||
return isset($this->map[$m[1]]) ? $this->map[$m[1]] : $m[1];
|
||||
}
|
||||
|
||||
private function buildPatterns() {
|
||||
$keys = array_keys($this->map);
|
||||
// длинные первыми: иначе короткий ключ перехватит часть длинной фразы
|
||||
usort($keys, function($a, $b) {
|
||||
$la = mb_strlen($a, 'UTF-8'); $lb = mb_strlen($b, 'UTF-8');
|
||||
if ($la === $lb) { return strcmp($a, $b); }
|
||||
return ($la < $lb) ? 1 : -1;
|
||||
});
|
||||
$this->patterns = array();
|
||||
// режем на группы, чтобы не упереться в предел размера шаблона PCRE
|
||||
foreach (array_chunk($keys, 150) as $chunk) {
|
||||
$quoted = array();
|
||||
foreach ($chunk as $k) { $quoted[] = preg_quote($k, '~'); }
|
||||
$this->patterns[] = '~(?<!\p{L})(' . implode('|', $quoted) . ')(?!\p{L})~u';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Перевод строки в шаблонах и контроллерах: t('Сохранить').
|
||||
* Поддерживает подстановку как sprintf: t('Осталось %d дней', $n).
|
||||
*/
|
||||
function t($text) {
|
||||
$lang = Lang::instance();
|
||||
$out = $lang ? $lang->get($text) : $text;
|
||||
if (func_num_args() > 1) {
|
||||
$args = func_get_args();
|
||||
$args[0] = $out;
|
||||
$formatted = @call_user_func_array('sprintf', $args);
|
||||
if (is_string($formatted) && $formatted !== '') {
|
||||
return $formatted;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ require_once(ENGINE_DIR . 'main/model.php');
|
||||
|
||||
require_once(ENGINE_DIR . 'main/registry.php');
|
||||
require_once(ENGINE_DIR . 'main/config.php');
|
||||
require_once(ENGINE_DIR . 'main/lang.php');
|
||||
require_once(ENGINE_DIR . 'main/game_settings.php');
|
||||
require_once(ENGINE_DIR . 'main/request.php');
|
||||
require_once(ENGINE_DIR . 'main/session.php');
|
||||
@@ -28,6 +29,12 @@ $registry = new Registry();
|
||||
$config = new Config();
|
||||
$registry->config = $config;
|
||||
|
||||
// Язык интерфейса. Перевод применяется к готовому ответу (см. engine/main/lang.php),
|
||||
// поэтому весь вывод собирается в буфер — включая страницы, которые делают exit().
|
||||
$lang = new Lang($registry);
|
||||
$registry->lang = $lang;
|
||||
ob_start(array($lang, 'apply'));
|
||||
|
||||
$game_settings = new Game_settings();
|
||||
$registry->game_settings = $game_settings;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
**Русский** · [English](SECURITY.en.md)
|
||||
|
||||
# Аудит безопасности: что нашли и что исправили
|
||||
|
||||
Аудит исходников сборки HostinPL 5.6 («nulled»-копия). Проверялось: скрытые закладки и вебшеллы,
|
||||
|
||||
+25
-2
@@ -1,3 +1,5 @@
|
||||
**Русский** · [English](CHANGES.en.md)
|
||||
|
||||
# Что изменено по сравнению с оригиналом
|
||||
|
||||
Оригинал: **HostinPL 5.6** в виде «nulled»-сборки (форк `Xopowblu-4EJlOBEK/HostinPL-5.6`),
|
||||
@@ -153,7 +155,28 @@ Node.js берётся из NodeSource **по HTTPS с проверкой клю
|
||||
|
||||
---
|
||||
|
||||
## 8. Брендинг и даты
|
||||
## 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`,
|
||||
@@ -169,7 +192,7 @@ Node.js берётся из NodeSource **по HTTPS с проверкой клю
|
||||
|
||||
---
|
||||
|
||||
## 9. phpMyAdmin
|
||||
## 10. phpMyAdmin
|
||||
|
||||
Панель ссылается на `/phpmyadmin` из админки. Оригинальный установщик ставил phpMyAdmin через
|
||||
Apache, что на nginx не работало. Теперь он ставится из репозитория дистрибутива и отдаётся
|
||||
|
||||
+54
-9
@@ -1,3 +1,5 @@
|
||||
**Русский** · [English](GUIDE.en.md)
|
||||
|
||||
# Полная инструкция
|
||||
|
||||
От пустого сервера до принимающего клиентов хостинга.
|
||||
@@ -12,10 +14,11 @@
|
||||
4. [Подключение ноды к панели](#4-подключение-ноды-к-панели)
|
||||
5. [Сборки игр](#5-сборки-игр)
|
||||
6. [Настройка панели](#6-настройка-панели)
|
||||
7. [Капча](#7-капча)
|
||||
8. [HTTPS и домен](#8-https-и-домен)
|
||||
9. [Обслуживание](#9-обслуживание)
|
||||
10. [Если что-то не работает](#10-если-что-то-не-работает)
|
||||
7. [Язык интерфейса](#7-язык-интерфейса)
|
||||
8. [Капча](#8-капча)
|
||||
9. [HTTPS и домен](#9-https-и-домен)
|
||||
10. [Обслуживание](#10-обслуживание)
|
||||
11. [Если что-то не работает](#11-если-что-то-не-работает)
|
||||
|
||||
---
|
||||
|
||||
@@ -219,7 +222,49 @@ Minecraft (Paper) — с `papermc.io`, RAGE:MP — с `rage.mp`, игры Steam
|
||||
|
||||
---
|
||||
|
||||
## 7. Капча
|
||||
## 7. Язык интерфейса
|
||||
|
||||
Панель говорит на **русском и английском**. Язык выбирается в таком порядке:
|
||||
|
||||
1. `?lang=en` или `?lang=ru` в адресе — явный выбор, запоминается в cookie на год
|
||||
2. cookie `lang` — прошлый выбор
|
||||
3. **заголовок браузера `Accept-Language`** — автоопределение
|
||||
4. параметр `lang` в `application/config.php` (по умолчанию `ru`)
|
||||
|
||||
Автоопределение намеренно оставляет русский для русскоязычных локалей (ru, uk, be, kk и других)
|
||||
и переключает на английский для всех остальных — то есть посетитель из Германии или Бразилии
|
||||
сразу получает английский интерфейс, ничего не нажимая.
|
||||
|
||||
**Переключатель** стоит в верхней панели кабинета и админки (кнопки `RU` / `EN` рядом с балансом)
|
||||
и в подвале страницы входа.
|
||||
|
||||
**Как это сделано.** Панель написана с русским текстом прямо в шаблонах — около 1500 строк в
|
||||
200 файлах. Вместо того чтобы переписывать их все, перевод применяется к готовому ответу:
|
||||
`index.php` оборачивает вывод в `ob_start()`, и перед отправкой браузеру строки заменяются по
|
||||
словарю (`engine/main/lang.php`). Это разом покрывает всю панель, включая админку и письма, а
|
||||
фразы, которых нет в словаре, просто остаются русскими — ничего не может пропасть с экрана.
|
||||
|
||||
Замена работает **только на границах слов**, поэтому короткий ключ не может испортить слово
|
||||
подлиннее (без этого «Мод» превращал «Модуль» в «Modуль»).
|
||||
|
||||
**Как добавить свой язык**, например немецкий:
|
||||
|
||||
```bash
|
||||
cp panel/application/lang/en.php panel/application/lang/de.php
|
||||
# перевести значения в de.php, затем добавить 'de' в Lang::AVAILABLE
|
||||
# в panel/engine/main/lang.php
|
||||
```
|
||||
|
||||
Полноту словаря мы проверяли обходом страниц: все 20 разделов, которые проходим, отдаются без
|
||||
остатков русского текста. Если увидите непереведённую фразу — допишите её в
|
||||
`application/lang/en.php`, ключ это русский текст ровно в том виде, в каком он на странице.
|
||||
|
||||
Перевод включается только когда язык не русский; в русском режиме словарь даже не загружается,
|
||||
поэтому накладных расходов нет.
|
||||
|
||||
---
|
||||
|
||||
## 8. Капча
|
||||
|
||||
По умолчанию **выключена** — регистрация и вход работают без неё, поля просто нет.
|
||||
|
||||
@@ -246,7 +291,7 @@ sudo service php8.4-fpm restart # подставьте свою версию
|
||||
|
||||
---
|
||||
|
||||
## 8. HTTPS и домен
|
||||
## 9. HTTPS и домен
|
||||
|
||||
Панель ставится на HTTP. Для сертификата:
|
||||
|
||||
@@ -269,7 +314,7 @@ sudo service php8.4-fpm restart
|
||||
|
||||
---
|
||||
|
||||
## 9. Обслуживание
|
||||
## 10. Обслуживание
|
||||
|
||||
**Резервные копии.** Сохраняйте базу и конфиг:
|
||||
|
||||
@@ -303,7 +348,7 @@ sudo service php8.4-fpm restart
|
||||
|
||||
---
|
||||
|
||||
## 10. Если что-то не работает
|
||||
## 11. Если что-то не работает
|
||||
|
||||
**«Ошибка: Не удалось загрузить контроллер …»** — панель не нашла контроллер для этого адреса.
|
||||
Обычно это ссылка на то, чего нет: например `/phpmyadmin`, когда phpMyAdmin не установлен
|
||||
@@ -314,7 +359,7 @@ sudo service php8.4-fpm restart
|
||||
Частая причина: `short_open_tag = Off`. Панель использует короткие теги `<? ?>`, установщик
|
||||
включает эту опцию сам, но при переустановке PHP её нужно вернуть.
|
||||
|
||||
**Не могу войти, на форме «Invalid site key»** — включена капча без ключей, см. раздел 7.
|
||||
**Не могу войти, на форме «Invalid site key»** — включена капча без ключей, см. раздел 8.
|
||||
|
||||
**Планировщик не работает** (статистика не обновляется, просроченные сервера не отключаются):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user