docs(deploy): .env.example + secrets.md, проброс OpenIddict env в compose (P0-8)

deploy/.env.example — все required/опц. переменные (POSTGRES_PASSWORD, REGISTRY,
*_TAG, OPENIDDICT_ISSUER/CERT_PASSWORD, FM_* бэкапа, Cors/RateLimiting/MoySklad).
docs/secrets.md — таблица переменных, где живут секреты (SMTP в БД, сертификаты в
volume), ротация, гигиена. compose: api получает OpenIddict__Issuer (за прокси
обязателен) и OpenIddict__CertPassword из .env. compose config валиден.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
nns 2026-05-27 02:51:13 +05:00
parent 5b981dd34b
commit 45326281f9
3 changed files with 100 additions and 0 deletions

43
deploy/.env.example Normal file
View file

@ -0,0 +1,43 @@
# food-market — пример переменных окружения для деплоя.
#
# Скопировать в deploy/.env и заполнить значениями (.env в .gitignore — НЕ коммитить).
# cp deploy/.env.example deploy/.env && $EDITOR deploy/.env
#
# docker-compose читает deploy/.env автоматически. Описание секретов и ротация —
# docs/secrets.md. Ключи OpenIddict — docs/openiddict-keys.md.
# ─── Реестр образов и теги (docker-compose) ──────────────────────────────────
# Откуда тянуть образы. Локальный registry на stage — 127.0.0.1:5001 (см. CLAUDE/memory).
REGISTRY=127.0.0.1:5001
API_TAG=latest
WEB_TAG=latest
PUBLIC_TAG=latest
# ─── База данных (ОБЯЗАТЕЛЬНО) ───────────────────────────────────────────────
# Пароль пользователя food_market в Postgres-контейнере. Подставляется и в
# POSTGRES_PASSWORD контейнера БД, и в ConnectionStrings__Default API.
# Сгенерировать: openssl rand -base64 24
POSTGRES_PASSWORD=CHANGE_ME_strong_db_password
# ─── OpenIddict / выдача токенов ─────────────────────────────────────────────
# Публичный URL админки = issuer токенов (обязателен за nginx-прокси).
OPENIDDICT_ISSUER=https://admin.food-market.kz/
# Пароль PFX-сертификатов подписи/шифрования. Пусто = без пароля (self-signed
# генерируется автоматически в App_Data, если файлов нет). Подробности — docs/openiddict-keys.md.
OPENIDDICT_CERT_PASSWORD=
# ─── Бэкап (systemd food-market-backup.*) ────────────────────────────────────
# Переопределения для скрипта бэкапа. По умолчанию совпадают с compose — можно не задавать.
# FM_BACKUP_DIR=/opt/food-market-data/backups
# FM_UPLOADS_DIR=/opt/food-market-data/uploads
# FM_BACKUP_RETENTION_DAYS=30
# ─── Прочее (опционально, переопределяет appsettings.json) ───────────────────
# CORS-origins фронта (если отличается от зашитых в appsettings). Индексируется с 0:
# Cors__AllowedOrigins__0=https://admin.food-market.kz
# Антибрутфорс на /connect/token и /api/auth/signup (дефолты 5/мин, 20/час):
# RateLimiting__Enabled=true
# RateLimiting__PerMinute=5
# RateLimiting__PerHour=20
# Интеграция МойСклад (по умолчанию боевой api.moysklad.ru):
# MoySklad__BaseUrl=https://api.moysklad.ru/api/remap/1.2/

View file

@ -29,6 +29,11 @@ services:
environment:
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__Default: Host=postgres;Port=5432;Database=food_market;Username=food_market;Password=${POSTGRES_PASSWORD:-food_market_dev}
# Публичный issuer токенов — обязателен за прокси, иначе берётся из запроса
# (или дефолт localhost из appsettings, что неверно для прод).
OpenIddict__Issuer: ${OPENIDDICT_ISSUER:-https://admin.food-market.kz/}
# Пароль PFX-сертификатов OpenIddict (пусто = сертификаты без пароля).
OpenIddict__CertPassword: ${OPENIDDICT_CERT_PASSWORD:-}
# Host port mapping: pick free ports on existing stage server (80/443 taken by
# legacy nginx, 5000/5002/5005 taken by legacy .NET apps).
ports:

52
docs/secrets.md Normal file
View file

@ -0,0 +1,52 @@
# Секреты и переменные окружения
Все секреты задаются через `deploy/.env``.gitignore`, **не коммитится**).
Шаблон со всеми переменными — `deploy/.env.example`. docker-compose читает `.env`
автоматически из каталога запуска (`deploy/`).
```bash
cp deploy/.env.example deploy/.env
$EDITOR deploy/.env # заполнить значения
chmod 600 deploy/.env # ограничить доступ
```
## Перечень
| Переменная | Обяз. | Назначение | Где используется | Как получить |
|---|:---:|---|---|---|
| `POSTGRES_PASSWORD` | ✅ | пароль БД `food_market` | контейнер postgres + `ConnectionStrings__Default` API | `openssl rand -base64 24` |
| `REGISTRY` | ✅ | реестр образов | image-ссылки в compose | стейдж: `127.0.0.1:5001` |
| `API_TAG` / `WEB_TAG` / `PUBLIC_TAG` | ✅ | теги образов | image-ссылки | тег из CI / `latest` |
| `OPENIDDICT_ISSUER` | ✅(прод) | публичный issuer токенов | API `OpenIddict__Issuer` | публичный URL админки, напр. `https://admin.food-market.kz/` |
| `OPENIDDICT_CERT_PASSWORD` | — | пароль PFX-сертификатов | API `OpenIddict__CertPassword` | свой пароль или пусто (self-signed без пароля) |
| `FM_BACKUP_DIR` / `FM_UPLOADS_DIR` / `FM_BACKUP_RETENTION_DAYS` | — | параметры бэкапа | `food-market-backup.sh` | дефолты совпадают с compose |
| `Cors__AllowedOrigins__N` | — | CORS-origins | API | переопределяет `appsettings.json` |
| `RateLimiting__*` | — | антибрутфорс лимиты | API | дефолты 5/мин, 20/час |
| `MoySklad__BaseUrl` | — | база API МойСклад | API | дефолт боевой `api.moysklad.ru` |
> `__` (двойное подчёркивание) — разделитель секций конфигурации .NET
> (`OpenIddict__Issuer` ≡ `OpenIddict:Issuer`).
## Где ещё живут секреты
- **SMTP (отправка писем)**НЕ в env. Хранятся в БД (`platform_settings`),
правятся из SuperAdmin-консоли (раздел «Платформа → SMTP»). Перечитываются на
каждой отправке без рестарта (см. `MailKitEmailSender`).
- **Сертификаты OpenIddict** — PFX в volume `api-data` (`/app/App_Data`). Генерируются
self-signed при отсутствии. Можно принести свои — см. [openiddict-keys.md](openiddict-keys.md).
- **Учётки БД/Forgejo на сервере** — вне репозитория (см. приватные заметки оператора).
## Ротация
| Секрет | Как ротировать | Влияние |
|---|---|---|
| `POSTGRES_PASSWORD` | `ALTER USER food_market PASSWORD '…'`, обновить `.env`, `docker compose up -d` | рестарт API |
| OpenIddict-сертификаты | заменить/удалить PFX, рестарт API | все токены инвалидируются — повторный логин |
| SMTP-пароль | через SuperAdmin-консоль | без рестарта |
## Гигиена
- `deploy/.env` — права `600`, владелец — пользователь деплоя.
- Не логировать значения секретов. Serilog настроен без дампа окружения.
- При утечке — ротировать затронутый секрет (таблица выше) и пересоздать токены.
- Проверка, что секреты не утекли в git: `git ls-files | grep -E '\.env$'` должен быть пуст.