b241c79773
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>
335 lines
7.6 KiB
Markdown
335 lines
7.6 KiB
Markdown
# Лидерра 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=<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
|
|
|
|
```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 <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.
|
|
|
|
```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` |
|