Audit #2 Phase 14 P2 fix. Covers: system requirements, DB setup (ICU collation + roles + migrations + grants), partition bootstrap, frontend build, Supervisor queue config, cron scheduler, Nginx, health checks, rolling update sequence, rollback, dev seed, common issues. cspell-words.txt +mbstring +pcntl (PHP ext names). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.6 KiB
Лидерра 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:
cp app/.env.example app/.env
Critical variables:
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=<secret>
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=<secret>
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
CACHE_STORE=redis
MAIL_MAILER=smtp
MAIL_HOST=smtp.unisender.com
MAIL_PORT=587
MAIL_USERNAME=<unisender-go-api-key>
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@liderra.ru
MAIL_FROM_NAME="Лидерра"
3. Database Setup
3.1 Create database with ICU collation
-- Run as PostgreSQL superuser
CREATE DATABASE liderra
ENCODING 'UTF8'
LOCALE_PROVIDER 'icu'
ICU_LOCALE 'ru-RU'
TEMPLATE template0;
3.2 Create application roles
# 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
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
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:
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)
-- Run as PostgreSQL superuser
CREATE EXTENSION IF NOT EXISTS pgaudit;
4. Application Bootstrap
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
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:
[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
supervisorctl reread
supervisorctl update
supervisorctl start liderra-worker:*
7. Scheduler (Cron)
Add to the deployment user's crontab (crontab -e):
* * * * * 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)
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
# 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=productionphp artisan schedule:list— 9 entries includingpartitions:create-months- Redis:
PONG - HTTP
/login: 200
10. Deployment Sequence (Rolling Update)
# 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
# Revert to previous release tag
git checkout <previous-tag>
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.
pg_dump -U crm_migrator liderra > backup_$(date +%Y%m%d_%H%M%S).sql
12. Development Seed
For staging/dev environments only:
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 |