@@ -0,0 +1,631 @@
# Тестовый деплой портала Лидерра в Yandex Cloud — план
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task (inline — план содержит интерактивные шаги заказчика: создание VM, DNS, deploy-key). Steps use checkbox (`- [ ]`) syntax.
**Goal: ** Поднять рабочую копию портала в интернете на одной Linux-VM в Yandex Cloud по адресу `https://<поддомен>` с HTTPS, доступом только для заказчика+Claude, для ручного теста.
**Architecture: ** Одна Ubuntu 24.04 VM: nginx (HTTPS + Basic Auth) → PHP-FPM 8.3 → портал (Laravel 13 + собранный Vue) → PostgreSQL 16 + Redis 7 на той же машине; queue worker + scheduler как systemd-службы. Фронтенд собирается на dev-машине и заливается. Настоящие роли БД (RLS включён). Спека: `docs/superpowers/specs/2026-05-21-test-deploy-yandex-cloud-design.md` .
**Tech Stack: ** Yandex Cloud Compute, Ubuntu 24.04 LTS, nginx, PHP 8.3-FPM, PostgreSQL 16, Redis 7, Certbot/Let's Encrypt, systemd, OpenSSH.
**Условные обозначения: ** 🧑 = шаг заказчика (веб-интерфейс/решение), 🤖 = шаг Claude (Bash/SSH). Плейсхолдеры: `<SERVER_IP>` , `<DOMAIN>` (например `test.example.ru` ), `<BASIC_USER>` /`<BASIC_PASS>` (дверь сайта) — заполняются по ходу.
---
## Фаза 0 — Подготовка на dev-машине (🤖, до создания сервера)
### Task 0.1: Проверить SSH-клиент и сгенерировать ключ деплоя
**Files: ** `~/.ssh/liderra_deploy` , `~/.ssh/liderra_deploy.pub` (на dev-машине)
- [ ] **Step 1: Проверить наличие OpenSSH **
Run: `ssh -V; ssh-keygen --help 2>&1 | Select-Object -First 1`
Expected: версия OpenSSH (например `OpenSSH_for_Windows_9.x` ). Если нет — поставить «OpenSSH Client» через Settings → Optional Features.
- [ ] **Step 2: Сгенерировать ключ-пару (без пароля, ed25519) **
Run (PowerShell):
``` powershell
ssh-keygen -t ed25519 -f " $env:USERPROFILE \.ssh\liderra_deploy " -C " liderra-test-deploy " -N '""'
```
Expected: созданы `liderra_deploy` (приватный) и `liderra_deploy.pub` (публичный).
- [ ] **Step 3: Показать публичный ключ заказчику **
Run: `Get-Content "$env:USERPROFILE\.ssh\liderra_deploy.pub"`
Expected: строка `ssh-ed25519 AAAA... liderra-test-deploy` . Отдать заказчику для вставки при создании VM (Task 1.2).
### Task 0.2: Код-правка — временный флаг доступа к админке (TDD)
**Files: **
- Modify: `app/config/app.php` (добавить ключ `saas_admin_test_bypass` )
- Modify: `app/app/Http/Middleware/EnsureSaasAdmin.php`
- Test: `app/tests/Feature/Middleware/EnsureSaasAdminTest.php` (создать или дополнить)
- [ ] **Step 1: Написать падающий тест **
Создать `app/tests/Feature/Middleware/EnsureSaasAdminTest.php` :
``` php
< ? php
declare ( strict_types = 1 );
use function Pest\Laravel\get ;
it ( 'blocks admin area in production by default' , function () {
app () -> detectEnvironment ( fn () => 'production' );
config ([ 'app.saas_admin_test_bypass' => false ]);
// любой admin-маршрут под EnsureSaasAdmin; подставить реальный из routes
$response = get ( '/api/admin/tenants' );
expect ( $response -> status ()) -> toBe ( 503 );
});
it ( 'allows admin area in production when test bypass flag is on' , function () {
app () -> detectEnvironment ( fn () => 'production' );
config ([ 'app.saas_admin_test_bypass' => true ]);
$response = get ( '/api/admin/tenants' );
expect ( $response -> status ()) -> not -> toBe ( 503 );
});
```
- [ ] **Step 2: Запустить — убедиться, что падает **
Run: `cd app; C:\tools\php83\php.exe artisan test --filter=EnsureSaasAdmin`
Expected: второй тест FAIL (сейчас middleware всегда 503 вне local/testing).
- [ ] **Step 3: Добавить ключ конфига **
В `app/config/app.php` добавить (рядом с другими ключами):
``` php
'saas_admin_test_bypass' => ( bool ) env ( 'SAAS_ADMIN_TEST_BYPASS' , false ),
```
- [ ] **Step 4: Поправить middleware **
В `app/app/Http/Middleware/EnsureSaasAdmin.php` заменить тело `handle` :
``` php
public function handle ( Request $request , Closure $next ) : Response
{
if ( app () -> environment ( 'local' , 'testing' )) {
return $next ( $request );
}
// ВРЕМЕННО (тест-деплой): пропускаем при включённом флаге.
// TODO: убрать после внедрения Yandex 360 SSO (Б-1 + DO-4).
if ( config ( 'app.saas_admin_test_bypass' ) === true ) {
return $next ( $request );
}
abort ( 503 , 'SaaS-admin авторизация не настроена (ожидает Б-1 + DO-4).' );
}
```
- [ ] **Step 5: Запустить тест — зелёный **
Run: `cd app; C:\tools\php83\php.exe artisan test --filter=EnsureSaasAdmin`
Expected: оба PASS.
- [ ] **Step 6: Линт + commit **
Run: `cd app; composer pint; composer stan`
Expected: 0 ошибок.
``` bash
git add app/config/app.php app/app/Http/Middleware/EnsureSaasAdmin.php app/tests/Feature/Middleware/EnsureSaasAdminTest.php
git commit -m "feat(deploy): temporary SAAS_ADMIN_TEST_BYPASS flag for test server (off by default)"
```
> NB: маршрут `/api/admin/tenants` в тесте — подставить реальный admin-маршрут из `app/routes/`. Уточнить на Step 1 (grep по `EnsureSaasAdmin`).
### Task 0.3: Собрать фронтенд для прода
- [ ] **Step 1: Прод-сборка **
Run: `npm --prefix app run build`
Expected: создан `app/public/build/` с манифестом и ассетами, ошибок нет.
- [ ] **Step 2: Зафиксировать факт сборки **
Сборка не коммитится (build в .gitignore) — будет залита на сервер в Task 3.3 через scp. Проверить: `Test-Path app/public/build/manifest.json` → True.
---
## Фаза 1 — Создание сервера (🧑 заказчик в консоли YC, по инструкции Claude)
### Task 1.1: Зарезервировать статический публичный IP
- [ ] **Step 1: ** YC Console → Virtual Private Cloud → IP-адреса → «Зарезервировать адрес» → зона `ru-central1-a` .
- [ ] **Step 2: ** Записать выданный IP → это `<SERVER_IP>` (нужен для DNS; статический, чтобы адрес не менялся при перезагрузке).
### Task 1.2: Создать виртуальную машину
- [ ] **Step 1: ** Compute Cloud → «Создать ВМ».
- [ ] **Step 2: ** Параметры:
- Имя: `liderra-test` ; зона `ru-central1-a` .
- Образ: **Ubuntu 24.04 LTS ** .
- vCPU 2, RAM 2 ГБ, **гарантированная доля vCPU 20% ** (дёшево; сборки идут на dev-машине).
- Диск: SSD 20 ГБ.
- Публичный адрес: выбрать **зарезервированный ** из Task 1.1.
- Доступ: логин `deploy` ; SSH-ключ — вставить публичный ключ из Task 0.1 Step 3.
- [ ] **Step 3: ** Создать. Дождаться статуса RUNNING.
### Task 1.3: Открыть порты (группа безопасности)
- [ ] **Step 1: ** VPC → Группы безопасности → группа сети ВМ → правила входящего трафика.
- [ ] **Step 2: ** Разрешить TCP **22, 80, 443 ** (источник `0.0.0.0/0` ; 22 можно сузить до IP заказчика/dev — но для простоты теста оставить открытым).
- [ ] **Step 3: ** Сообщить Claude `<SERVER_IP>` → переходим к Фазе 2.
---
## Фаза 2 — Базовая настройка сервера (🤖 по SSH)
### Task 2.1: Первое подключение
- [ ] **Step 1: Подключиться **
Run: `ssh -i "$env:USERPROFILE\.ssh\liderra_deploy" -o StrictHostKeyChecking=accept-new deploy@<SERVER_IP> "echo OK; lsb_release -d"`
Expected: `OK` + `Ubuntu 24.04` .
- [ ] **Step 2: Обновить пакеты **
Run: `ssh ... deploy@<SERVER_IP> "sudo apt-get update && sudo apt-get -y upgrade"`
Expected: завершается без ошибок.
### Task 2.2: Установить стек
- [ ] **Step 1: Установить пакеты **
Run одной командой по SSH:
``` bash
sudo apt-get install -y nginx \
php8.3-fpm php8.3-cli php8.3-pgsql php8.3-redis php8.3-mbstring \
php8.3-xml php8.3-curl php8.3-bcmath php8.3-zip php8.3-gd php8.3-intl \
postgresql postgresql-contrib redis-server git unzip certbot python3-certbot-nginx \
apache2-utils
```
Expected: установлено без ошибок (`apache2-utils` даёт `htpasswd` ).
- [ ] **Step 2: Установить Composer **
``` bash
php -r "copy('https://getcomposer.org/installer','/tmp/ci.php');" \
&& sudo php /tmp/ci.php --install-dir= /usr/local/bin --filename= composer
```
Run: `ssh ... "composer --version; php -v | head -1"`
Expected: Composer 2.x; PHP 8.3.
- [ ] **Step 3: Проверить службы **
Run: `ssh ... "systemctl is-active nginx php8.3-fpm postgresql redis-server"`
Expected: `active` × 4.
---
## Фаза 3 — База, код, конфиг (🤖 по SSH)
> **Порядок исполнения внутри фазы:** 3.2 (код на сервере — db/-скрипты приезжают с репо) → 3.1 (БД и роли) → 3.3 (фронтенд) → 3.4 (.env) → 3.5 (схема через migrate + grants + seed). Здесь нумерация по смыслу, но db-скрипты есть только после clone.
>
> **DB-роли (из `db/00_create_roles.sql` v1.1 + `app/config/database.php`):** пароли передаются psql через `-v` (НЕ `ALTER ROLE`). Схема грузится миграцией `load_initial_schema` (она делает `DB::unprepared(schema.sql)`) под ролью `crm_migrator` (BYPASSRLS+CREATEDB). Гранты — `db/02_grants.sql`. Рантайм — `crm_app_user` (RLS). Supplier-джобы — `crm_supplier_worker` (BYPASSRLS) через connection `pgsql_supplier`. Connection `pgsql_migrator` в конфиге НЕТ → для миграций временно подменяем `DB_USERNAME` на `crm_migrator` (default-connection `pgsql`), потом возвращаем на `crm_app_user`.
### Task 3.1: Создать БД и роли
**Files (на сервере): ** `db/00_create_roles.sql` (после clone в 3.2).
- [ ] **Step 1: Сгенерировать пароли ролей (на dev или сервере) **
Run: `ssh ... "for r in app admin migrator audit supplier; do echo \$r=\$(openssl rand -hex 16); done"`
Expected: 5 строк вида `app=...` . Сохранить как `<APP_DB_PASS>` / `<ADMIN_DB_PASS>` / `<MIGRATOR_DB_PASS>` / `<AUDIT_DB_PASS>` / `<WORKER_DB_PASS>` (в безопасное место, не в git).
- [ ] **Step 2: Создать БД **
``` bash
ssh ... "sudo -u postgres createdb liderra"
```
Expected: без ошибок.
- [ ] **Step 3: Создать роли с паролями (через -v) **
``` bash
ssh ... "sudo -u postgres psql -d liderra \
-v crm_app_password='<APP_DB_PASS>' \
-v crm_admin_password='<ADMIN_DB_PASS>' \
-v crm_migrator_password='<MIGRATOR_DB_PASS>' \
-v crm_audit_writer_password='<AUDIT_DB_PASS>' \
-v crm_supplier_worker_password='<WORKER_DB_PASS>' \
-f /var/www/liderra/db/00_create_roles.sql"
```
Run: `ssh ... "sudo -u postgres psql -d liderra -c '\du' | grep -E 'crm_(app|migrator|supplier)'"`
Expected: 5 ролей созданы (`crm_app_user` , `crm_admin_user` , `crm_migrator` , `crm_audit_writer` , `crm_supplier_worker` ).
- [ ] **Step 4: Разрешить TCP-вход ролям (pg_hba) **
> Роли ходят через 127.0.0.1 (scram). Убедиться, что `pg_hba.conf` имеет строку `host all all 127.0.0.1/32 scram-sha-256` (на Ubuntu по умолчанию есть). Если нет — добавить и `sudo systemctl reload postgresql`.
Run: `ssh ... "sudo grep -E '127.0.0.1/32' /etc/postgresql/16/main/pg_hba.conf"`
Expected: строка с `scram-sha-256` (или `md5` ).
### Task 3.2: Выложить код (deploy-key + clone)
- [ ] **Step 1: Сгенерировать deploy-key на сервере **
``` bash
ssh ... "ssh-keygen -t ed25519 -f ~/.ssh/github_deploy -N '' -C 'liderra-server'; cat ~/.ssh/github_deploy.pub"
```
Expected: публичный ключ сервера.
- [ ] **Step 2 (🧑): Добавить ключ в GitHub **
Заказчик: GitHub → репо `CoralMinister/lidpotok` → Settings → Deploy keys → Add → вставить ключ (read-only, без write).
- [ ] **Step 3: Настроить SSH для GitHub + clone **
``` bash
ssh ... 'cat >> ~/.ssh/config <<EOF
Host github.com
IdentityFile ~/.ssh/github_deploy
StrictHostKeyChecking accept-new
EOF
sudo mkdir -p /var/www && sudo chown deploy:deploy /var/www
git clone git@github.com:CoralMinister/lidpotok.git /var/www/liderra
cd /var/www/liderra && git checkout main && git log -1 --oneline'
```
Expected: репозиторий склонирован, HEAD на нужном коммите (с флагом из Task 0.2 — убедиться, что коммит влит в `main` ; иначе `git checkout <ветка>` ).
- [ ] **Step 4: composer install **
``` bash
ssh ... "cd /var/www/liderra/app && composer install --no-dev --optimize-autoloader --no-interaction"
```
Expected: зависимости установлены, 0 ошибок.
### Task 3.3: Залить собранный фронтенд
- [ ] **Step 1: Скопировать build на сервер **
Run (с dev-машины):
``` powershell
scp -i " $env:USERPROFILE \.ssh\liderra_deploy " -r app / public / build deploy @ < SERVER_IP > : / var / www / liderra / app / public /
```
Expected: `manifest.json` + ассеты на сервере.
### Task 3.4: Production .env
- [ ] **Step 1: Создать .env на сервере **
``` bash
ssh ... 'cat > /var/www/liderra/app/.env <<EOF
APP_NAME=Liderra
APP_ENV=production
APP_DEBUG=false
APP_URL=https://<DOMAIN>
APP_LOCALE=ru
APP_FALLBACK_LOCALE=ru
APP_TIMEZONE=Europe/Moscow
LOG_CHANNEL=stack
LOG_LEVEL=warning
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=liderra
DB_USERNAME=crm_app_user
DB_PASSWORD=<APP_DB_PASS>
DB_SUPPLIER_USERNAME=crm_supplier_worker
DB_SUPPLIER_PASSWORD=<WORKER_DB_PASS>
SESSION_DRIVER=redis
SESSION_LIFETIME=120
QUEUE_CONNECTION=redis
CACHE_STORE=redis
REDIS_CLIENT=predis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_FROM_ADDRESS="hello@<DOMAIN>"
MAIL_FROM_NAME=Liderra
SAAS_ADMIN_TEST_BYPASS=true
AUTH_PASSWORD_RESET_TOKEN_TABLE=password_resets
EOF'
```
- [ ] **Step 2: APP_KEY **
``` bash
ssh ... "cd /var/www/liderra/app && php artisan key:generate --force && php artisan about | head -20"
```
Expected: ключ сгенерирован; `Environment: production` , `Debug Mode: OFF` .
### Task 3.5: Схема (migrate), гранты, демо-данные, кэши
> Схему и сиды грузим под BYPASSRLS-ролью `crm_migrator`, потом возвращаем рантайм на `crm_app_user`. Подмена — временно правим `DB_USERNAME`/`DB_PASSWORD` в `.env` (это значения для default-connection `pgsql`, через которую идёт migrate/seed).
- [ ] **Step 1: Временно переключить .env на crm_migrator **
``` bash
ssh ... "cd /var/www/liderra/app && \
sed -i 's/^DB_USERNAME=.*/DB_USERNAME=crm_migrator/; s/^DB_PASSWORD=.*/DB_PASSWORD=<MIGRATOR_DB_PASS>/' .env && \
grep -E '^DB_(USERNAME|PASSWORD)=' .env"
```
Expected: `DB_USERNAME=crm_migrator` .
- [ ] **Step 2: Накатить схему (миграция load_initial_schema грузит schema.sql) **
``` bash
ssh ... "cd /var/www/liderra/app && php artisan migrate --force"
```
Run: `ssh ... "sudo -u postgres psql -d liderra -c '\dt' | tail -3"`
Expected: миграция `load_initial_schema` отработала; десятки таблиц (схема v8.27).
- [ ] **Step 3: Создать партиции (как на dev — ручной cron вместо pg_partman) **
``` bash
ssh ... "cd /var/www/liderra/app && php artisan partitions:create-months"
```
Expected: партиции созданы (команда из ЭТАЛОН/project_phase1_strategy; если имя иное — `php artisan list | grep partition` ).
- [ ] **Step 4: Применить гранты **
``` bash
ssh ... "sudo -u postgres psql -d liderra -f /var/www/liderra/db/02_grants.sql"
```
Expected: гранты применены без ошибок (запуск под postgres-суперюзером — владелец/superuser, см. 00_create_roles doc вариант с crm_admin_user тоже подходит).
- [ ] **Step 5: Демо-данные (под crm_migrator, BYPASSRLS — cross-tenant сид проходит) **
``` bash
# залить нужные демо-скрипты на сервер
scp -i " $env :USERPROFILE\.ssh\liderra_deploy " app/storage/_demo_5users.php app/storage/_demo_split_tenants.php deploy@<SERVER_IP>:/var/www/liderra/app/storage/
ssh ... "cd /var/www/liderra/app && php artisan db:seed --force && php artisan tinker storage/_demo_5users.php && php artisan tinker storage/_demo_split_tenants.php"
```
Expected: 5 компаний + учётки `admin@demo.local` / `manager1..4@demo.local` (пароль `password` ).
> NB: точный набор демо-скриптов сверить с ЭТАЛОН §4 (там же команда восстановления). Залить только нужные `_demo_*.php`.
- [ ] **Step 6: Вернуть рантайм-роль crm_app_user **
``` bash
ssh ... "cd /var/www/liderra/app && \
sed -i 's/^DB_USERNAME=.*/DB_USERNAME=crm_app_user/; s/^DB_PASSWORD=.*/DB_PASSWORD=<APP_DB_PASS>/' .env && \
grep -E '^DB_USERNAME=' .env"
```
Expected: `DB_USERNAME=crm_app_user` (RLS будет enforce'иться в рантайме).
- [ ] **Step 7: Права и кэши **
``` bash
ssh ... 'cd /var/www/liderra/app \
&& sudo chown -R deploy:www-data storage bootstrap/cache \
&& sudo chmod -R 775 storage bootstrap/cache \
&& php artisan config:cache && php artisan route:cache && php artisan view:cache'
```
Expected: кэши собраны, прав хватает.
---
## Фаза 4 — Веб, HTTPS, дверь (🤖 + 🧑 DNS)
### Task 4.1: DNS A-запись (🧑)
- [ ] **Step 1: ** В панели домена создать запись `A` для `<DOMAIN>` → `<SERVER_IP>` .
- [ ] **Step 2 (🤖): Проверить распространение **
Run: `ssh ... "getent hosts <DOMAIN> || nslookup <DOMAIN>"`
Expected: резолвится в `<SERVER_IP>` (может занять до 30–60 мин).
### Task 4.2: nginx vhost (HTTP)
- [ ] **Step 1: Конфиг сайта **
``` bash
ssh ... 'sudo tee /etc/nginx/sites-available/liderra <<EOF
server {
listen 80;
server_name <DOMAIN>;
root /var/www/liderra/app/public;
index index.php;
# дверь на весь сайт (Basic Auth), кроме webhook поставщика
location / {
auth_basic "Liderra test";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files \$uri \$uri/ /index.php?\$query_string;
}
location ^~ /api/webhook/ {
auth_basic off;
try_files \$uri \$uri/ /index.php?\$query_string;
}
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/liderra /etc/nginx/sites-enabled/liderra
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx'
```
Expected: `nginx -t` syntax ok; reload без ошибок.
> NB: точный префикс webhook (`/api/webhook/`) сверить с `app/routes/api.php` (grep `webhook`). Если иной — поправить `location ^~`.
- [ ] **Step 2: Создать пароль двери **
``` bash
ssh ... "sudo htpasswd -bc /etc/nginx/.htpasswd <BASIC_USER> <BASIC_PASS>"
```
Expected: `.htpasswd` создан.
- [ ] **Step 3: Проверка по HTTP **
Run: `ssh ... "curl -s -o /dev/null -w '%{http_code}' -u <BASIC_USER>:<BASIC_PASS> http://<DOMAIN>/"`
Expected: `200` (или `302` на /login). Без креда → `401` .
### Task 4.3: HTTPS (Let's Encrypt)
- [ ] **Step 1: Выпустить сертификат **
``` bash
ssh ... "sudo certbot --nginx -d <DOMAIN> --non-interactive --agree-tos -m <EMAIL> --redirect"
```
Expected: сертификат выпущен, nginx переписан на 443 + редирект с 80.
- [ ] **Step 2: Проверить HTTPS + авто-продление **
Run: `ssh ... "curl -sI -u <BASIC_USER>:<BASIC_PASS> https://<DOMAIN>/ | head -1; sudo certbot renew --dry-run 2>&1 | tail -1"`
Expected: `HTTP/2 200|302` ; dry-run `Congratulations` / success.
---
## Фаза 5 — Фоновые службы (🤖)
### Task 5.1: queue worker как systemd-служба
- [ ] **Step 1: Юнит **
``` bash
ssh ... 'sudo tee /etc/systemd/system/liderra-queue.service <<EOF
[Unit]
Description=Liderra queue worker
After=redis-server.service postgresql.service
[Service]
User=deploy
Restart=always
WorkingDirectory=/var/www/liderra/app
ExecStart=/usr/bin/php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now liderra-queue'
```
Run: `ssh ... "systemctl is-active liderra-queue"`
Expected: `active` .
### Task 5.2: scheduler (cron)
- [ ] **Step 1: Cron-запись **
``` bash
ssh ... '( crontab -l 2>/dev/null; echo "* * * * * cd /var/www/liderra/app && /usr/bin/php artisan schedule:run >> /dev/null 2>&1" ) | crontab -'
```
Run: `ssh ... "crontab -l | grep schedule:run"`
Expected: строка присутствует.
---
## Фаза 6 — Приёмка и сопровождение (🤖)
### Task 6.1: Проверка критериев готовности (DoD)
- [ ] **Step 1: HTTPS + замочек **
Открыть `https://<DOMAIN>` в браузере (с логином двери) → валидный сертификат, портал грузится.
- [ ] **Step 2: Дверь работает **
Run: `ssh ... "curl -s -o /dev/null -w '%{http_code}' https://<DOMAIN>/"` → `401` (без креда).
- [ ] **Step 3: Вход + данные **
В браузере: `admin@demo.local` / `password` → видно 4 демо-проекта.
- [ ] **Step 4: Изоляция компаний (RLS) **
Войти `manager1@demo.local` / `password` → видна только своя компания (чужих проектов нет). Если падает SQL — зафиксировать, чинить (риск из спеки §5.4).
- [ ] **Step 5: Админка **
Открыть `/admin/...` под админом → не 503 (флаг bypass работает).
- [ ] **Step 6: Службы переживают перезагрузку **
``` bash
ssh ... "sudo reboot" # подождать ~40с
ssh ... "systemctl is-active nginx php8.3-fpm postgresql redis-server liderra-queue"
```
Expected: все `active` ; сайт снова открывается.
### Task 6.2: Скрипт обновления + инструкция
**Files: ** `/var/www/liderra/deploy.sh` (на сервере), `docs/deploy/test-server-runbook.md` (в репо)
- [ ] **Step 1: deploy.sh **
``` bash
ssh ... 'cat > /var/www/liderra/deploy.sh <<EOF
#!/usr/bin/env bash
set -euo pipefail
cd /var/www/liderra
git pull
cd app
composer install --no-dev --optimize-autoloader --no-interaction
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
sudo systemctl restart php8.3-fpm liderra-queue
echo "Deployed: \$(git -C /var/www/liderra log -1 --oneline)"
EOF
chmod +x /var/www/liderra/deploy.sh'
```
> Фронтенд при обновлении: пересобрать на dev (`npm --prefix app run build`) и `scp` build на сервер ПЕРЕД запуском deploy.sh.
- [ ] **Step 2: Runbook **
Создать `docs/deploy/test-server-runbook.md` : адрес, доступы (где лежат пароли), команда обновления, как остановить/удалить VM (прекратить оплату), напоминание убрать `SAAS_ADMIN_TEST_BYPASS` при переходе к настоящему SSO.
- [ ] **Step 3: Commit runbook **
``` bash
git add docs/deploy/test-server-runbook.md
git commit -m "docs(deploy): test-server runbook"
```
---
## Открытые вопросы (заполнить при исполнении)
- `<DOMAIN>` и панель управления доменом — от заказчика.
- Точный admin-маршрут для теста (Task 0.2) и префикс webhook (Task 4.2) — grep по коду.
- Точные seed-шаги демо-учёток (Task 3.5) — по ЭТАЛОН §4.
- Пароли БД-ролей (`<APP_DB_PASS>` , `<ADMIN_DB_PASS>` , `<MIGRATOR_DB_PASS>` , `<AUDIT_DB_PASS>` , `<WORKER_DB_PASS>` ) + дверь сайта (`<BASIC_PASS>` ) — сгенерировать (Task 3.1 Step 1), сохранить в безопасном месте (не в git; занести в runbook-ссылку на хранилище).
- `pg_hba.conf` путь зависит от версии PG (`/etc/postgresql/16/main/` ) — сверить на сервере.