Files
lauadminandClaude Opus 5.5 2b7a92645a
CI / backend (pull_request) Successful in 3m7s
CI / frontend (pull_request) Failing after 15m55s
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>
2026-09-25 20:52:20 +03:00

8.2 KiB
Raw Permalink Blame History

Выкладка в прод

Где что

Что Где
Сервер 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). Во всех командах ниже:

C="docker compose -f docker-compose.yml -f docker-compose.prod.yml"

Обычная выкладка

  1. Изменения попадают в main только через PR в Gitea; CI (.gitea/workflows/ci.yml) должен быть зелёным.

  2. На сервере:

    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 — пересобрать и перезапустить без новых коммитов.

После выкладки:

$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 недоступен несколько секунд, а воркер перезапускается.

Откат

Код:

cd ~/lux_fiscal
git log --oneline -10
git checkout --detach <коммит>
$C up -d --build --wait

Следующий deploy.sh сам вернётся на main. Правильный путь исправления — revert-коммит через PR.

БД из бэкапа (всё, что было после бэкапа, потеряется):

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.

  • Бэкапы лежат на том же диске, что и БД, — периодически копируйте их за пределы сервера:

    scp "lux-prod:backups/lux_fiscal/*.dump" D:/Backups/lux_fiscal/
    

Прод-данные локально

Прод-дамп содержит зашифрованные ключи касс, а локальный .env — ключи CRM. Чтобы локальный воркер не создавал боевые чеки и не менял статусы заказов в CRM, локально всегда:

ENVIRONMENT=local
CHECKBOX_USE_STUB=true
CRM_USE_STUB=true

После восстановления прод-дампа в локальную БД сразу обнулите ключи касс (лицензия/PIN — фиктивные, ключ НП — NULL):

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.