Add CRM stub flag, deploy/backup scripts, CI and prod runbook
- CRM_USE_STUB: local runs no longer reach the live CRM. With only the Checkbox stub, a stub receipt would still move the real order to PACKED. Refused in production, same as CHECKBOX_USE_STUB. - scripts/deploy.sh: backup, fast-forward main, build, health check and code rollback on failure. scripts/backup.sh: pg_dump with verification and 14-day rotation (used by cron and deploy.sh). - Gitea Actions CI: ruff + pytest, oxlint + build. - DEPLOY.md runbook; CLAUDE.md rules for safe local development. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# Выкладка в прод
|
||||
|
||||
## Где что
|
||||
|
||||
| Что | Где |
|
||||
|---|---|
|
||||
| Сервер | `websrv`, `192.168.88.100`, Ubuntu; SSH: пользователь `deploy`, порт 22 (алиас `lux-prod` в `~/.ssh/config` разработчика) |
|
||||
| Приложение | `https://asist.ystyle.com.ua` |
|
||||
| Код | `/home/deploy/lux_fiscal` — клон `main` из Gitea (deploy key только на чтение, `~/.ssh/config` → `Host gitea` = `127.0.0.1:2222`) |
|
||||
| Секреты | `/home/deploy/lux_fiscal/.env` (права 600, в git не попадает) |
|
||||
| Бэкапы | `/home/deploy/backups/lux_fiscal/*.dump`, журнал — `backup.log` там же |
|
||||
| TLS / домен | Nginx Proxy Manager на том же сервере (`http://192.168.88.100:81`), Proxy Host `asist.ystyle.com.ua` → `lux-fiscal-frontend:80` |
|
||||
|
||||
Сервисы поднимаются из двух файлов: `docker-compose.yml` + `docker-compose.prod.yml` (прод-оверлей убирает порты на хосте
|
||||
и подключает `frontend` к внешней сети `web`, где живёт NPM). Во всех командах ниже:
|
||||
|
||||
```bash
|
||||
C="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
|
||||
```
|
||||
|
||||
## Обычная выкладка
|
||||
|
||||
1. Изменения попадают в `main` только через PR в Gitea; CI (`.gitea/workflows/ci.yml`) должен быть зелёным.
|
||||
2. На сервере:
|
||||
|
||||
```bash
|
||||
ssh lux-prod
|
||||
~/lux_fiscal/scripts/deploy.sh
|
||||
```
|
||||
|
||||
`deploy.sh` делает: бэкап БД (`scripts/backup.sh`) → `git fetch` + fast-forward `main` → `up -d --build --wait`
|
||||
(миграции применяет одноразовый контейнер `migrate`) → проверка `/api/v1/health` через nginx фронтенда.
|
||||
Если новая версия не поднялась — откатывает **код** на предыдущий коммит и печатает путь к бэкапу.
|
||||
**БД автоматически не откатывается.** `--force` — пересобрать и перезапустить без новых коммитов.
|
||||
|
||||
После выкладки:
|
||||
|
||||
```bash
|
||||
$C ps
|
||||
$C logs --since 10m api worker | grep -iE "error|exception" | tail -20
|
||||
```
|
||||
|
||||
## Правила, чтобы не сломать прод
|
||||
|
||||
- **Миграции — только совместимые с данными.** Новые колонки — `nullable` или с `server_default`. Удаление/переименование
|
||||
колонки — отдельным релизом, после того как код перестал её использовать (иначе откат кода не спасёт).
|
||||
- Перед мержем миграцию прогоняют локально на копии свежего прод-бэкапа (см. «Прод-данные локально»).
|
||||
- `ENCRYPTION_KEY` в прод-`.env` **никогда не меняется**: им зашифрованы ключи касс в БД. Копия `.env` хранится отдельно от
|
||||
сервера (менеджер паролей).
|
||||
- В проде запрещены `CHECKBOX_USE_STUB=true` и `CRM_USE_STUB=true` — приложение упадёт на старте клиента.
|
||||
- Руками на сервере файлы репозитория не правятся: `deploy.sh` откажется работать с грязным деревом.
|
||||
- Выкладка — не в часы пик работы кассиров: во время `up` API недоступен несколько секунд, а воркер перезапускается.
|
||||
|
||||
## Откат
|
||||
|
||||
Код:
|
||||
|
||||
```bash
|
||||
cd ~/lux_fiscal
|
||||
git log --oneline -10
|
||||
git checkout --detach <коммит>
|
||||
$C up -d --build --wait
|
||||
```
|
||||
|
||||
Следующий `deploy.sh` сам вернётся на `main`. Правильный путь исправления — revert-коммит через PR.
|
||||
|
||||
БД из бэкапа (всё, что было после бэкапа, потеряется):
|
||||
|
||||
```bash
|
||||
cd ~/lux_fiscal
|
||||
$C stop api worker
|
||||
$C exec -T postgres sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists --no-owner' \
|
||||
< ~/backups/lux_fiscal/<файл>.dump
|
||||
$C start api worker
|
||||
```
|
||||
|
||||
Код при этом должен соответствовать версии схемы в дампе (`alembic_version`).
|
||||
|
||||
## Бэкапы
|
||||
|
||||
- `scripts/backup.sh`: `pg_dump -Fc` → проверка `pg_restore --list` → ротация 14 дней.
|
||||
- Cron пользователя `deploy` (`crontab -l`): каждый день в 03:30 UTC.
|
||||
- Бэкапы лежат на том же диске, что и БД, — периодически копируйте их за пределы сервера:
|
||||
|
||||
```bash
|
||||
scp "lux-prod:backups/lux_fiscal/*.dump" D:/Backups/lux_fiscal/
|
||||
```
|
||||
|
||||
## Прод-данные локально
|
||||
|
||||
Прод-дамп содержит зашифрованные ключи касс, а локальный `.env` — ключи CRM. Чтобы локальный воркер не создавал боевые
|
||||
чеки и не менял статусы заказов в CRM, локально **всегда**:
|
||||
|
||||
```ini
|
||||
ENVIRONMENT=local
|
||||
CHECKBOX_USE_STUB=true
|
||||
CRM_USE_STUB=true
|
||||
```
|
||||
|
||||
После восстановления прод-дампа в локальную БД сразу обнулите ключи касс (лицензия/PIN — фиктивные, ключ НП — `NULL`):
|
||||
|
||||
```bash
|
||||
docker compose run --rm --no-deps -T api python - <<'EOF'
|
||||
import asyncio
|
||||
from sqlalchemy import text
|
||||
from app.core import crypto
|
||||
from app.core.config import settings
|
||||
from app.db.session import engine
|
||||
assert not settings.is_production
|
||||
async def main():
|
||||
async with engine.begin() as c:
|
||||
await c.execute(text("update cash_registers set license_key_enc=:l, cashier_pin_enc=:p, np_api_key_enc=null"),
|
||||
{"l": crypto.encrypt("local-stub-license"), "p": crypto.encrypt("0000")})
|
||||
asyncio.run(main())
|
||||
EOF
|
||||
```
|
||||
|
||||
Зашифрованы только ключи касс, и скрипт их перезаписывает — поэтому локальный `ENCRYPTION_KEY` может быть любым.
|
||||
Делайте это до первого запуска локальных `api`/`worker`.
|
||||
|
||||
Варнинги воркера `ettn_poll_failed … не знайдено` на прод-копии — норма: стаб Checkbox не знает ID настоящих чеков.
|
||||
|
||||
## CI (Gitea Actions)
|
||||
|
||||
`.gitea/workflows/ci.yml`: backend — `ruff check` + `pytest`; frontend — `npm run lint` + `npm run build`.
|
||||
Workflow выполняется только при зарегистрированном `act_runner`:
|
||||
|
||||
1. Gitea → Site Administration → Actions → Runners → Create new Runner — скопировать registration token.
|
||||
2. Запустить раннер контейнером рядом с Gitea (образ `gitea/act_runner`, переменные `GITEA_INSTANCE_URL`,
|
||||
`GITEA_RUNNER_REGISTRATION_TOKEN`, проброс `/var/run/docker.sock`).
|
||||
|
||||
Сервер слабый (2.5 ГБ RAM): при выкладке во время прогона CI сборка фронтенда может упереться в память.
|
||||
|
||||
## Первичная установка (для справки)
|
||||
|
||||
1. Пользователь `deploy` в группе `docker`, SSH-ключ разработчика в `~deploy/.ssh/authorized_keys` (700/600, владелец `deploy`).
|
||||
2. Deploy key `~deploy/.ssh/gitea_deploy` добавлен в Gitea (repo → Settings → Deploy Keys, только чтение);
|
||||
`git clone gitea:lauadmin/lux_fiscal.git ~/lux_fiscal`.
|
||||
3. `.env` из `.env.example`: `ENVIRONMENT=production`, `DEBUG=false`, `BASE_URL`/`CORS_ORIGINS` = `https://asist.ystyle.com.ua`,
|
||||
сгенерированные `SECRET_KEY`/`POSTGRES_PASSWORD`, `ENCRYPTION_KEY` — тот, которым зашифрованы ключи касс в переносимой БД.
|
||||
4. `$C up -d --build --wait`; на пустой БД — `$C run --rm api python -m app.cli bootstrap`.
|
||||
5. NPM: Proxy Host → `lux-fiscal-frontend:80`, Let's Encrypt, Force SSL.
|
||||
6. Cron: `30 3 * * * $HOME/lux_fiscal/scripts/backup.sh >> $HOME/backups/lux_fiscal/backup.log 2>&1`.
|
||||
Reference in New Issue
Block a user