Установка редакции PHP
Редакция PHP кладётся на виртуальный хостинг как есть: ни Composer, ни менеджера пакетов, ни службы, которую надо держать запущенной. Одна папка, один файл настроек, одно задание планировщика. Это руководство ведёт от пустой папки до рабочего экземпляра, включая то, что большинство руководств обходит стороной: права доступа, TLS, задание планировщика, резервные копии и обновление.
Что понадобится#
| Составляющая | Версия | Зачем |
|---|---|---|
| PHP | 8.1 или новее | Объединения типов, enum, readonly, never: код ими пользуется. |
pdo_sqlite | — | Вся база целиком. Без неё ничего не работает. |
mbstring | — | Правильная работа со строками на шестнадцати языках. |
openssl | рекомендуется | Шифрование секретов в базе (пароли SMTP, токены внешнего хранения). |
curl | рекомендуется | Исходящие веб-перехватчики и отправка копий третьей стороне. |
intl | рекомендуется | Даты пишутся как в редакции Node. Без неё названия месяцев берутся из словарей. |
Мастер установки проверяет всё это и показывает на экране, прежде чем пустить вас дальше: то, что препятствует, останавливает установку; то, что необязательно, отмечается без остановки.
Ставить через composer нечего: чтение IMAP, архив tar.gz, PDF,
CSV, QR-код и HTTP-клиент написаны внутри продукта. Именно это позволяет положить его на
хостинг, где у вас нет командной строки.
1. Выложить файлы#
Возьмите архив редакции PHP и выложите его. Папка выглядит так:
toutadmin/
├── app/ код (никогда не отдаётся веб-сервером)
│ ├── Core/ ядро: маршрутизация, база, сессии, безопасность
│ ├── Modules/ предметная часть: кадры, финансы, проекты, качество…
│ ├── Controllers/
│ ├── views/ шаблоны
│ ├── locales/ шестнадцать словарей
│ └── schema.sql схема, та же, что в редакции Node
├── public/ ← корень сайта указывает сюда, и никуда больше
│ ├── index.php входной контроллер
│ ├── css/ js/
├── data/ база, сейф, папка на подпись, поступившие документы, копии
├── tools/ cron.php, check-keys.php
├── tests/
├── config.sample.php
└── VERSION
public/, а не на папку
Это самый важный пункт страницы. Если сервер отдаёт папку целиком,
data/app.sqlite становится доступен для скачивания кому угодно: вся база,
включая хеши паролей и сейф. Папка data/ лежит выше public/
намеренно.
На виртуальном хостинге#
Бывает два случая. Если хостер позволяет выбрать корень домена (cPanel, Plesk и большинство
хостингов в режиме «корневая папка»), положите папку вне www/ и направьте домен
на toutadmin/public.
Если корень навязан — часто www/ или public_html/ — положите туда
содержимое public/, а остальное выше, и поправьте путь в начале
public/index.php:
www/ ← навязанный корень
├── index.php
├── css/ js/
toutadmin/ ← остальное, вне досягаемости веба
├── app/
├── data/
└── config.php
// www/index.php — строка, которую надо поправить
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';
В крайнем случае, если выше корня положить нечего, поставляемый файл .htaccess
внутри data/ уже запрещает доступ. Это ремень, а не решение: всегда предпочитайте
папку вне корня.
2. Написать настройки#
cp config.sample.php config.php
Затем откройте config.php:
<?php
return [
// База SQLite и папка данных: вне корня сайта.
'db_path' => __DIR__ . '/data/app.sqlite',
'data_dir' => __DIR__ . '/data',
// Ключ, принадлежащий этому экземпляру. Создаётся один раз:
// php -r "echo bin2hex(random_bytes(32));"
'session_secret' => '…',
// Установочный токен: см. следующий шаг.
'install_token' => '',
// Публичный адрес, для ссылок, отправляемых почтой.
'base_url' => 'https://intranet.primer.ru',
// Включать только за доверенным прокси.
'trust_proxy' => false,
// Пределы: попытки входа за четверть часа с одного адреса,
// запросы в минуту с одного адреса.
'login_rate_limit' => 10,
'global_rate_limit' => 300,
// Истечение: бездействие, затем предельный срок, который ничто не продлевает.
'session_idle_minutes' => 60,
'session_max_hours' => 12,
];
Он скрепляет подписи в папке на подпись и шифрует секреты в базе. Потерять его — значит
обесценить подписи. Если оставить значение из образца, продукт создаст ключ сам в
data/.instance-key (права 0600) — тогда не забудьте включить этот
файл в резервные копии.
Настройки можно держать и в другом месте: переменная окружения
TOUTADMIN_CONFIG указывает на другой файл.
3. Поставить установочный токен#
Между выкладкой файлов и вашим приходом в мастер экземпляр принадлежит тому, кто его нашёл: кто пришёл первым, тот и создаёт учётную запись администратора. Окно короткое, но его хватает. Поэтому задайте случайное значение до выхода в свет:
php -r "echo bin2hex(random_bytes(16));"
# затем в config.php:
'install_token' => 'c3f1…',
Мастер спрашивает его, сравнивает за постоянное время — обычное сравнение позволило бы угадывать токен посимвольно — и записывает каждый отказ. Без токена ничего не создаётся. После установки мастер закрывается сам: токен может остаться, он больше ни на что не влияет.
4. Права доступа#
| Путь | Права | Кто пишет |
|---|---|---|
data/ | 0770 | Веб-сервер, и только он |
data/app.sqlite | 0660 | Создаётся установкой |
config.php | 0640 | Никто — серверу только чтение |
app/, public/ | 0755 | Никто — только чтение |
# пример на сервере, где PHP работает под www-data
sudo chown -R вы:www-data /var/www/toutadmin
sudo find /var/www/toutadmin -type d -exec chmod 750 {} \;
sudo find /var/www/toutadmin -type f -exec chmod 640 {} \;
sudo chmod 770 /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/public
SQLite пишет ещё и соседние файлы (-wal, -shm): доступной для
записи должна быть папка, а не только база.
5. Веб-сервер#
Nginx#
server {
listen 443 ssl http2;
server_name intranet.primer.ru;
root /var/www/toutadmin/public; # и ничего больше
index index.php;
ssl_certificate /etc/letsencrypt/live/intranet.primer.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.primer.ru/privkey.pem;
client_max_body_size 20M; # загрузки в сейф, поступившие документы
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Загруженные файлы никогда не отдаются напрямую: они проходят
# через маршрут, который проверяет сессию и контрольную сумму.
location ~ ^/(data|app|tools|tests)/ { deny all; }
}
server {
listen 80;
server_name intranet.primer.ru;
return 301 https://$host$request_uri;
}
Apache#
<VirtualHost *:443>
ServerName intranet.primer.ru
DocumentRoot /var/www/toutadmin/public
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/intranet.primer.ru/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/intranet.primer.ru/privkey.pem
<Directory /var/www/toutadmin/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Поставляемый файл public/.htaccess уже направляет все адреса на
index.php. На виртуальном хостинге с Apache этого достаточно.
TLS#
Продукт выставляет заголовок Strict-Transport-Security, как только соединение
зашифровано, и никогда открытым текстом: объявленный с незашифрованной страницы он не был бы
прочитан, а локальную пробу запер бы в https на полгода. На сервере, которым вы
управляете:
sudo certbot --nginx -d intranet.primer.ru
За прокси или балансировщиком, который заканчивает TLS, поставьте
'trust_proxy' => true — и только там: иначе любой сможет объявить в заголовке
IP-адрес и протокол по своему выбору.
6. Задание планировщика#
Сайт на PHP работает только в момент запроса. То, что редакция Node делает в своём часовом обходе — восемь операций — здесь делается из планировщика хостинга:
* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
| Что делает задание | С какой частотой |
|---|---|
| Закрывает учётные записи с истёкшим договором | при каждом проходе |
| Выставляет счета по подписке, срок которых настал | при каждом проходе, никогда дважды |
| Превращает сроки в уведомления | при каждом проходе, без повторов |
| Чистит прочитанные уведомления и журнал аудита | согласно выбранному сроку хранения |
| Забирает бухгалтерский почтовый ящик (IMAP) | если приём настроен |
| Опустошает очередь веб-перехватчиков с их повторами | при каждом проходе |
| Создаёт автоматическую копию и выносит её наружу | когда истёк заданный промежуток |
Вызывать сценарий чаще заданного промежутка не значит копировать чаще: до срока ничего не
запускается. На хостинге, где планировщик ограничен одним проходом в четверть часа, замените
* * * * * на */15 * * * * — веб-перехватчики просто уйдут с этой
задержкой.
Некоторые начальные тарифы его не дают. Вызов по HTTP из стороннего сервиса (сторож
доступности и тому подобное) не подходит: cron.php отказывается выполняться
иначе как из командной строки — именно чтобы публичный адрес не мог запустить резервное
копирование. В этом случае копия запускается вручную с экрана Резервные копии.
7. Мастер установки#
Откройте свой домен. Пока нет ни одной учётной записи, любой адрес перенаправляет на
/installation. Мастер умещается на одной странице:
- Требования, проверенные и показанные — препятствующее помечено как таковое.
- Установочный токен, если он задан.
- Организация: название, которое видно всюду, и число дней годового отпуска, назначаемое каждому новому штатному сотруднику.
- Язык по умолчанию для экземпляра, из 16.
- Учётная запись администратора: адрес и пароль не короче двенадцати знаков.
Всё пишется одной транзакцией. Как только учётная запись появляется,
/installation перенаправляет на страницу входа: мастер закрылся сам, вручную
удалять нечего.
8. Проверить, что всё работает#
# тесты, на машине (в вашу базу они не пишут)
php tests/run.php
# переводы: ни один экран не использует ключ, которого нет в словарях
php tools/check-keys.php
Затем в интерфейсе: заведите сотрудника, войдите под ним, загрузите вложение, запустите копию вручную и проверьте её на экране Резервные копии. Эти четыре действия затрагивают базу, файлы, права и архив — то есть всё, что хостинг должен уметь.
9. Обновление#
# 1. сначала резервная копия, всегда
php tools/cron.php # или экран «Резервные копии»
# 2. заменить код, а не данные
# app/ public/ tools/ tests/ VERSION ← заменяются
# data/ config.php ← остаются как есть
# 3. база обновляется сама при первом запросе
Схема развивается идемпотентными миграциями: повторить обновление ничего не ломает. Экземпляр редакции Node и экземпляр редакции PHP делят одну схему — 142 таблиц, 1311 столбцов — и одни пароли: база переходит из одной в другую без преобразования.
10. Резервировать по-настоящему#
Архив tar.gz несёт базу (скопированную через VACUUM INTO, а значит
согласованную даже во время записи) и пять папок с файлами: фотографии профиля, поступившие
резюме, сейф, папку на подпись, поступившие документы. Каждый файл несёт свою контрольную
сумму SHA-256, проверяемую при восстановлении.
Архив, оставшийся на сервере, который он защищает, не защищает ничего: настройте внешнее хранилище (FTPS или Google Drive) на экране Резервные копии. Подробности — на странице Резервное копирование и восстановление.
Сначала попробовать локально#
cp config.sample.php config.php
php -S localhost:8000 -t public public/index.php
Встроенного сервера PHP достаточно, чтобы познакомиться с продуктом. Для работы он не годится: единственный процесс, нет TLS, нет ограничения нагрузки.
Документация Toutadmin — собрана 2026-09-13. Самостоятельный сайт, независимый от программы.