# Лидерра CRM — Production Deployment Runbook **Version:** 1.0 от 2026-05-13 **Stack:** Laravel 13 · Vue 3 + Vuetify 3 · PostgreSQL 16 · Redis 7 · PHP 8.3 **Cloud:** Yandex Cloud, region `ru-central1` --- ## 1. System Requirements | Component | Version | Notes | |---|---|---| | PHP | 8.3+ | Extensions: pdo_pgsql, pgsql, redis, bcmath, mbstring, openssl, tokenizer, xml, ctype, fileinfo, pcntl | | PostgreSQL | 16 | ICU collation support required (`--with-icu` compile flag) | | Redis | 7.x | Sessions, queues, cache | | Node.js | 20+ | Frontend build only | | Composer | 2.x | | | Supervisor | 4.x | Queue worker process management | --- ## 2. Environment Configuration Copy `.env.example` to `.env` and set all required values: ```bash cp app/.env.example app/.env ``` Critical variables: ```ini APP_ENV=production APP_KEY= # php artisan key:generate APP_URL=https://crm.example.com DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=liderra DB_USERNAME=crm_migrator # migration role — full DDL rights DB_PASSWORD= REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD= QUEUE_CONNECTION=redis SESSION_DRIVER=redis CACHE_STORE=redis MAIL_MAILER=smtp MAIL_HOST=smtp.unisender.com MAIL_PORT=587 MAIL_USERNAME= MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=noreply@liderra.ru MAIL_FROM_NAME="Лидерра" ``` --- ## 3. Database Setup ### 3.1 Create database with ICU collation ```sql -- Run as PostgreSQL superuser CREATE DATABASE liderra ENCODING 'UTF8' LOCALE_PROVIDER 'icu' ICU_LOCALE 'ru-RU' TEMPLATE template0; ``` ### 3.2 Create application roles ```bash # Run as PostgreSQL superuser psql -U postgres liderra < db/00_create_roles.sql ``` The script creates 5 roles: `crm_app_user`, `crm_app_admin`, `crm_readonly`, `crm_migrator`, `crm_supplier_worker` (BYPASSRLS). ### 3.3 Run migrations ```bash cd app php artisan migrate --force ``` This loads `db/schema.sql` (v8.19+) via the single bootstrap migration `load_initial_schema.php`. ### 3.4 Apply grants ```bash psql -U postgres liderra < db/02_grants.sql ``` ### 3.5 Create initial partition tables Partitioned tables (`lead_audit_log`, `supplier_session_log`, etc.) require month-based child partitions to exist before any inserts: ```bash cd app php artisan partitions:create-months ``` Run this once after migration. The scheduler maintains partitions automatically thereafter (see §7). ### 3.6 Apply pg_audit extension (pre-prod) ```sql -- Run as PostgreSQL superuser CREATE EXTENSION IF NOT EXISTS pgaudit; ``` --- ## 4. Application Bootstrap ```bash cd app # Install PHP dependencies composer install --no-dev --optimize-autoloader # Generate app key (first deploy only) php artisan key:generate --force # Clear and cache config/routes/views php artisan config:cache php artisan route:cache php artisan view:cache # Run storage symlink php artisan storage:link ``` --- ## 5. Frontend Build ```bash cd app npm ci npm run build ``` Output goes to `app/public/build/`. Confirm `app/public/build/manifest.json` exists. --- ## 6. Queue Worker (Supervisor) Create `/etc/supervisor/conf.d/liderra-worker.conf`: ```ini [program:liderra-worker] process_name=%(program_name)s_%(process_num)02d command=php /path/to/app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 autostart=true autorestart=true stopasgroup=true killasgroup=true user=www-data numprocs=2 redirect_stderr=true stdout_logfile=/var/log/liderra-worker.log stopwaitsecs=3600 ``` ```bash supervisorctl reread supervisorctl update supervisorctl start liderra-worker:* ``` --- ## 7. Scheduler (Cron) Add to the deployment user's crontab (`crontab -e`): ```cron * * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1 ``` The scheduler runs these jobs automatically: | Command/Job | Schedule | Purpose | |---|---|---| | `projects:reset-delivered-today` | daily 00:00 MSK | Reset daily lead counter | | `projects:reset-monthly` | 1st of month 00:00 MSK | Reset monthly counter for tier lookup | | `partitions:create-months` | daily | Create PostgreSQL partition tables for upcoming months | | `RefreshSupplierSessionJob` | hourly + 20:15 MSK | Supplier API session tokens | | `SyncSupplierProjectsJob` | 20:30 MSK daily | Sync supplier project list | | `CleanupInactiveSupplierProjectsJob` | 02:00 MSK daily | Archive stale supplier projects | | `supplier:retry-failed` | hourly | Retry failed supplier lead deliveries | | `CsvReconcileJob` | hourly | CSV reconciliation (reserve lead intake channel) | --- ## 8. Web Server (Nginx) ```nginx server { listen 443 ssl; server_name crm.example.com; root /path/to/app/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~* \.(js|css|png|jpg|svg|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } } ``` --- ## 9. Health Checks ```bash # PHP and Laravel bootstrap cd app && php artisan about # Database connection php artisan db:show # Scheduler registered entries php artisan schedule:list # Queue worker status supervisorctl status liderra-worker:* # Redis connection redis-cli -h 127.0.0.1 ping # Application HTTP curl -I https://crm.example.com/login ``` Expected responses: - `php artisan about` — no errors, APP_ENV=production - `php artisan schedule:list` — 9 entries including `partitions:create-months` - Redis: `PONG` - HTTP `/login`: 200 --- ## 10. Deployment Sequence (Rolling Update) ```bash # 1. Pull latest code git pull origin main # 2. Install/update dependencies cd app && composer install --no-dev --optimize-autoloader npm ci && npm run build # 3. Run new migrations (if any) php artisan migrate --force # 4. Recache configuration php artisan config:cache php artisan route:cache php artisan view:cache # 5. Restart queue workers (pick up new code) supervisorctl restart liderra-worker:* ``` --- ## 11. Rollback ```bash # Revert to previous release tag git checkout cd app composer install --no-dev --optimize-autoloader npm ci && npm run build php artisan migrate:rollback # only if the migration is reversible php artisan config:cache supervisorctl restart liderra-worker:* ``` > **Warning:** Schema migrations are not always reversible. Always take a PostgreSQL dump before deploying schema changes. ```bash pg_dump -U crm_migrator liderra > backup_$(date +%Y%m%d_%H%M%S).sql ``` --- ## 12. Development Seed For staging/dev environments only: ```bash cd app php artisan db:seed --class=DemoSeeder --force ``` Creates: `admin@demo.local` / `password`, 3 projects, 14 demo deals. **Never run DemoSeeder on production.** --- ## 13. Common Issues | Symptom | Likely Cause | Fix | |---|---|---| | `SQLSTATE[08006]` on boot | Wrong `DB_HOST` (use `127.0.0.1`, not `localhost` on Windows) | Set `DB_HOST=127.0.0.1` | | Partition insert error on new month | `partitions:create-months` not run | `php artisan partitions:create-months` | | Queue jobs not processing | Supervisor not running or wrong user | `supervisorctl status`; check `stdout_logfile` | | CSS/JS 404 after deploy | Frontend not rebuilt or `storage:link` missing | `npm run build` + `php artisan storage:link` | | `jwt expired` from supplier API | Supplier session not refreshed | `php artisan tinker` → `dispatch(new RefreshSupplierSessionJob)` | | Scheduler not running | Cron not set up | Verify crontab entry; `php artisan schedule:run --verbose` |