# Деплой Placeo на прод (HestiaCP, PHP 8.3, MariaDB)

Пошаговый перенос проекта. Предполагается: домен уже привязан в Hestia и открывается,
PHP 8.3 и MariaDB установлены. Везде ниже подставьте свои `USER` (Hestia-пользователь),
`placeo.ru` (домен) и пути.

Путь веб-домена в Hestia: `/home/USER/web/placeo.ru/`, докрут по умолчанию — `.../public_html`.

---

## 0. Локально: собрать фронтенд

Прод-сервер обычно без Node, поэтому ассеты собираем у себя и заливаем готовый `public/build`.

```bash
npm ci
npm run build          # создаёт public/build (манифест + css/js)
```

`public/build`, `public/storage` (симлинк создадим на сервере), `vendor/`, `.env`, `node_modules`
на сервер НЕ копируем (vendor поставим composer-ом, .env создадим на месте).

---

## 1. База данных в Hestia

Hestia → **DB** → Add Database. Создайте БД и пользователя (не используйте root!):

- DB: `USER_placeo` (Hestia сам добавляет префикс)
- User: `USER_placeo`, задайте надёжный пароль
- Тип: MySQL/MariaDB

Запомните имя БД, пользователя и пароль — пойдут в `.env`.

---

## 2. Залить код на сервер (в папку `private/`)

ВАЖНО про Hestia: `DOCUMENT_ROOT` домена жёстко указывает на `public_html`, а `index.php`
Laravel лежит в `public/`. Поэтому проект кладём НЕ внутрь `public_html`, а в Hestia-папку
`private/` (она вне веба и уже разрешена в `open_basedir` PHP-FPM), и делаем `public_html`
симлинком на `public/` Laravel (шаг 3).

> Почему `private/`, а не произвольная `app/`: дефолтный PHP-FPM-шаблон Hestia ограничивает
> `open_basedir` папками `public_html`, `private`, `public_shtml`, `tmp`. Папку-сосед вроде
> `app/` PHP просто не увидит. `private/` — в списке разрешённых и вне веб-корня.

Итоговая структура:

```
/home/USER/web/placeo.ru/
├── private/placeo/          ← весь Laravel (app, vendor, .env, public, ...)
│   └── public/index.php
└── public_html  →  private/placeo/public   (симлинк, шаг 3)
```

```bash
cd /home/USER/web/placeo.ru/
git clone <repo-url> private/placeo
# либо rsync -av ./ USER@server:/home/USER/web/placeo.ru/private/placeo/ (без vendor/node_modules/.env)
```

Залейте собранный `public/build` внутрь `private/placeo/public/build`, если он не в git.

Дальше во всех шагах рабочая папка проекта — `/home/USER/web/placeo.ru/private/placeo`.

---

## 3. Document root → Laravel public

Заменяем `public_html` симлинком на `public/` Laravel:

```bash
cd /home/USER/web/placeo.ru/
rm -rf public_html
ln -s private/placeo/public public_html
```

Теперь `DOCUMENT_ROOT` (`public_html`) физически указывает прямо в `private/placeo/public`,
где и лежит `index.php`. Конструкции `public_html/public` не возникает, а `.env`, `app/`,
`vendor/` остаются вне веб-корня.

Hestia по умолчанию работает связкой **nginx (прокси) + apache (backend)** — тогда
бандлевый `public/.htaccess` уже даёт нужные rewrite, ничего больше не требуется.

> Если вдруг `open_basedir` у вашего шаблона ещё строже (только `public_html`) — проверьте
> phpinfo или PHP-FPM-шаблон в Hestia (Web → домен → PHP-FPM template) и при необходимости
> переключитесь на шаблон без жёсткого `open_basedir` или добавьте путь `private` в пул.

Если домен на шаблоне **nginx + php-fpm** (без apache), добавьте Laravel-rewrite:
Hestia → Web → placeo.ru → правка nginx-шаблона (или кастомный конфиг) с блоком:

```nginx
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;   # сокет вашего пула
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    include fastcgi_params;
}
```

---

## 4. Файл .env на проде

```bash
cd /home/USER/web/placeo.ru/private/placeo
cp .env.example .env
nano .env
```

Ключевые значения для прода:

```dotenv
APP_NAME=Placeo
APP_ENV=production
APP_DEBUG=false
APP_URL=https://placeo.ru          # обязательно https — от него строятся sitemap/canonical/og и /storage

APP_LOCALE=ru

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=USER_placeo
DB_USERNAME=USER_placeo
DB_PASSWORD=пароль_из_шага_1

# Сессии/кеш/очередь — на БД (таблицы создаст migrate)
SESSION_DRIVER=database
CACHE_STORE=database
QUEUE_CONNECTION=database

FILESYSTEM_DISK=local              # фото и так пишутся на диск public явно

# Почта (нужна для кодов подтверждения при регистрации!) — впишите реальный SMTP
MAIL_MAILER=smtp
MAIL_HOST=smtp.вашпровайдер
MAIL_PORT=587
MAIL_USERNAME=...
MAIL_PASSWORD=...
MAIL_SCHEME=tls
MAIL_FROM_ADDRESS="noreply@placeo.ru"
MAIL_FROM_NAME="${APP_NAME}"

# Яндекс OAuth — redirect строится от APP_URL автоматически
YANDEX_CLIENT_ID=...
YANDEX_CLIENT_SECRET=...

# Модерация (DeepSeek). Включать ТОЛЬКО когда работает очередь (шаг 7)
DEEPSEEK_API_KEY=sk-...
MODERATION_ENABLED=true

# Telegram-уведомления (опционально)
TELEGRAM_BOT_TOKEN=...
TELEGRAM_ADMIN_CHAT_ID=...
```

> В Яндекс OAuth-приложении пропишите Callback URI: `https://placeo.ru/auth/yandex/callback`.

---

## 5. Зависимости и ключ приложения

Используйте PHP 8.3 (на Hestia часто `php8.3`). Composer — глобально или `composer.phar`.

```bash
cd /home/USER/web/placeo.ru/private/placeo
php8.3 /usr/local/bin/composer install --no-dev --optimize-autoloader
php8.3 artisan key:generate          # сгенерирует свежий APP_KEY (не переносите dev-ключ)
```

---

## 6. Миграции, симлинк хранилища, кеши

```bash
php8.3 artisan migrate --force                 # создаст таблицы (вкл. sessions, cache, jobs)
php8.3 artisan db:seed --force                 # ТОЛЬКО если нужны стартовые данные (категории и т.п.)
php8.3 artisan storage:link                    # public/storage → storage/app/public (фото объявлений)

php8.3 artisan config:cache
php8.3 artisan route:cache
php8.3 artisan view:cache
php8.3 artisan event:cache
# короткой командой всё сразу: php8.3 artisan optimize
```

> Любая правка `.env` на проде требует повторного `php8.3 artisan config:cache`.

---

## 7. Права доступа

Веб-серверу нужны на запись `storage/` и `bootstrap/cache/`:

```bash
cd /home/USER/web/placeo.ru/private/placeo
chown -R USER:USER storage bootstrap/cache
chmod -R ug+rwX storage bootstrap/cache
```

(Пул PHP-FPM в Hestia работает от пользователя домена `USER`, поэтому владелец — он.)

---

## 8. Очередь (воркер) — обязательно для модерации и уведомлений

Джобы (`ModerateAd`, `SendBatchedNewAdNotifications`, `SendTelegramAlert`) идут через очередь
на драйвере `database`. Нужен постоянный воркер. Надёжнее всего — systemd-сервис (от root):

`/etc/systemd/system/placeo-queue.service`:

```ini
[Unit]
Description=Placeo queue worker
After=network.target mariadb.service

[Service]
User=USER
Group=USER
Restart=always
WorkingDirectory=/home/USER/web/placeo.ru/private/placeo
ExecStart=/usr/bin/php8.3 -d disable_functions=pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_exec,pcntl_getpriority,pcntl_setpriority artisan queue:work --sleep=3 --tries=3 --max-time=3600
StartLimitIntervalSec=0

[Install]
WantedBy=multi-user.target
```

```bash
systemctl daemon-reload
systemctl enable --now placeo-queue
systemctl status placeo-queue
```

После каждого деплоя кода перезапускайте воркер, чтобы он подхватил новый код:
`php8.3 artisan queue:restart` (мягко) или `systemctl restart placeo-queue`.

> Нет доступа к systemd? Альтернатива — supervisor, либо cron-строка
> `* * * * * cd .../private/placeo && php8.3 artisan queue:work --stop-when-empty >> /dev/null 2>&1`
> (менее отзывчиво, но без демона).

---

## 9. Планировщик (cron)

Расписание (`routes/console.php`: авто-архив объявлений, прогрев sitemap, пакетные уведомления,
автообновление импортов, чистка журнала звонков) запускается раз в минуту. Задачу заводите
**в Hestia → Cron** (или `v-add-cron-job`), а не правкой crontab: Hestia пересобирает crontab из
своего `cron.conf` и ручные правки затрёт.

```cron
* * * * * /usr/bin/php8.3 -d disable_functions=pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_exec,pcntl_getpriority,pcntl_setpriority /home/USER/web/placeo.ru/private/placeo/artisan schedule:run
```

Поменять существующую задачу: `v-list-cron-jobs USER` → `v-change-cron-job USER JOB '*' '*' '*' '*' '*' 'КОМАНДА'`.

> ### ⚠ Ловушка с pcntl — без `-d disable_functions=…` не работает ни очередь, ни расписание
>
> В CLI-конфиге PHP под Hestia (`/etc/php/8.3/cli/php.ini`) в `disable_functions` запрещены все
> `pcntl_*`, а само расширение **загружено**. Laravel проверяет только `extension_loaded('pcntl')`,
> вызывает `pcntl_signal()` и получает фатальную ошибку:
>
> ```
> Call to undefined function Illuminate\Console\Scheduling\pcntl_signal()
> ```
>
> У планировщика это случается в `Event::run()` **перед каждой задачей** — задачи не выполняются
> вообще, а снаружи ничего не заметно: cron отрабатывает, в логе лишь строка на каждый запуск.
> На placeo.ru так прожили с 16.06 по 14.09.2026: не выполнилась ни одна задача по расписанию.
>
> Переопределение в командах выше заново задаёт список запретов **без** сигнальных функций
> (`pcntl_signal`, `pcntl_async_signals`, `pcntl_alarm`), оставляя запрещёнными опасные
> `fork`/`exec`/`wait`. Трогать общий `php.ini` не нужно, и на веб (PHP-FPM) это не влияет.
> Других функций, кроме `pcntl_*`, в списке запретов нет — переопределение ничего лишнего не открывает.
>
> Проверить, что расписание живо: `php8.3 -d disable_functions=… artisan schedule:test --name='sitemap:warm'`
> должна дойти до `DONE`; без переопределения та же команда падает на `pcntl_signal()`.

---

## 10. HTTPS

Hestia → Web → placeo.ru → **Enable SSL** + **Let's Encrypt** (и Force HTTPS).
В коде уже включён `URL::forceScheme('https')` для `APP_ENV=production`, поэтому все
ссылки (sitemap, canonical, og, /storage) будут https.

---

## 11. Финальная проверка

- `https://placeo.ru` — открывается, картинки объявлений грузятся (значит `storage:link` ок).
- `https://placeo.ru/robots.txt` — содержит `Sitemap:` и блокировки.
- `https://placeo.ru/sitemap.xml` — отдаёт XML, под-карты заполнены.
- Регистрация: приходит код на почту (SMTP настроен).
- Вход через Яндекс работает (redirect URI совпадает).
- Подача объявления → если `MODERATION_ENABLED=true`, объявление уходит в очередь и
  через секунды модерируется (проверьте, что воркер жив: `systemctl status placeo-queue`).
- Favicon отображается.
- Отправьте `sitemap.xml` в Яндекс.Вебмастер и Google Search Console.

---

## 12. Обновление кода в будущем (deploy через git)

Проект под git (GitLab, приватный, ветка `main`). Прод — git-чекаут этой ветки,
доступ read-only через deploy key (`/home/placeo/.ssh/gitlab_deploy`). `.env`, `vendor`,
`storage`, `public/storage` вне git и при `git pull` не затрагиваются.

**Локально** (рабочая машина):
```bash
# Если меняли Blade/Tailwind-классы — ОБЯЗАТЕЛЬНО пересобрать ассеты (на проде нет Node):
npm run build                       # public/build закоммичен в репозиторий
git add -A && git commit -m "…" && git push
```

**На проде** — один скрипт `/home/placeo/deploy-placeo.sh` (от пользователя placeo):
```bash
sudo -u placeo -H bash /home/placeo/deploy-placeo.sh
```
Он делает: `git pull --ff-only` → `composer install --no-dev --optimize-autoloader`
→ `php8.3 artisan migrate --force` → `php8.3 artisan optimize` → `php8.3 artisan queue:restart`.

> `public/build` хранится в репозитории (на проде нет Node), поэтому стили приезжают с `git pull`.
> Если забыть `npm run build` перед коммитом — новые Tailwind-классы не появятся (так когда-то «ломался подвал»).

**Откат:** `sudo -u placeo -H git -C /home/placeo/web/placeo.ru/private/placeo reset --hard <commit>`
затем `php8.3 artisan optimize`.
