feat: complete Epic 1 production foundation

This commit is contained in:
2026-08-25 01:24:11 +03:00
parent 767428436d
commit 182bde8ac0
298 changed files with 35719 additions and 5299 deletions
+47 -8
View File
@@ -17,7 +17,7 @@ Crank настраивается через переменные окружен
| `POSTGRES_ACQUIRE_TIMEOUT_MS` | `database.pool.acquire_timeout_ms` | `Shared` | `u64/milliseconds` | `5000` | `1..=300000` | `Public` | `Effective` |
| `POSTGRES_IDLE_TIMEOUT_MS` | `database.pool.idle_timeout_ms` | `Shared` | `u64/milliseconds` | `600000` | `1000..=86400000` | `Public` | `Effective` |
| `POSTGRES_MAX_LIFETIME_MS` | `database.pool.max_lifetime_ms` | `Shared` | `u64/milliseconds` | `1800000` | `1000..=86400000` | `Public` | `Effective` |
| `CRANK_MASTER_KEY` | `runtime.master_key` | `Shared` | `secret/-` | `required/blank` | `-` | `Secret` | `Effective` |
| `CRANK_MASTER_KEY` | `runtime.master_key` | `Shared` | `secret/-` | `required/blank` | `>=32` | `Secret` | `Effective` |
| `CRANK_BASE_URL` | `runtime.base_url` | `Shared` | `url/-` | `blank` | `-` | `Internal` | `Effective` |
| `CRANK_RUNTIME_MAX_CONCURRENT_UNARY` | `runtime.max_concurrent_unary` | `Shared` | `u32/requests` | `64` | `1..=65535` | `Public` | `Effective` |
| `CRANK_CACHE_BACKEND` | `cache.backend` | `Shared` | `enum/-` | `memory` | `-` | `Public` | `Effective` |
@@ -25,6 +25,7 @@ Crank настраивается через переменные окружен
| `CRANK_CACHE_DEFAULT_TTL_MS` | `cache.default_ttl_ms` | `Shared` | `u64/milliseconds` | `blank` | `1..=86400000` | `Public` | `DeprecatedNoEffect` |
| `CRANK_OUTBOUND_ALLOWED_HOSTS` | `outbound.allowed_hosts` | `Shared` | `host_list/-` | `` | `-` | `Internal` | `Effective` |
| `CRANK_OUTBOUND_DENIED_HOSTS` | `outbound.denied_hosts` | `Shared` | `host_list/-` | `` | `-` | `Internal` | `Effective` |
| `CRANK_OUTBOUND_MAX_REQUEST_BYTES` | `outbound.max_request_bytes` | `Shared` | `u64/bytes` | `4194304` | `1..=67108864` | `Public` | `Effective` |
| `CRANK_OUTBOUND_MAX_RESPONSE_BYTES` | `outbound.max_response_bytes` | `Shared` | `u64/bytes` | `4194304` | `1..=67108864` | `Public` | `Effective` |
| `CRANK_ENVIRONMENT` | `observability.environment` | `Shared` | `label/-` | `development` | `-` | `Public` | `Effective` |
| `CRANK_LOG_LEVEL` | `observability.log_filter` | `Shared` | `string/-` | `blank` | `-` | `Public` | `Effective` |
@@ -52,9 +53,10 @@ Crank настраивается через переменные окружен
| `CRANK_SESSION_SECRET` | `admin.session.secret` | `AdminApi` | `secret/-` | `required/blank` | `-` | `Secret` | `Effective` |
| `CRANK_PASSWORD_PEPPER` | `admin.password_pepper` | `AdminApi` | `secret/-` | `required/blank` | `-` | `Secret` | `Effective` |
| `CRANK_SESSION_TTL_HOURS` | `admin.session.ttl_hours` | `AdminApi` | `u32/hours` | `24` | `1..=8760` | `Public` | `Effective` |
| `CRANK_TRUST_FORWARDED_HEADERS` | `admin.trust_forwarded_headers` | `AdminApi` | `bool/-` | `false` | `-` | `Public` | `Effective` |
| `CRANK_TRUST_FORWARDED_HEADERS` | `admin.trust_forwarded_headers` | `AdminApi` | `bool/-` | `blank` | `-` | `Public` | `DeprecatedNoEffect` |
| `CRANK_TRUSTED_PROXY_IPS` | `admin.trusted_proxy_ips` | `AdminApi` | `ip_list/-` | `` | `-` | `Internal` | `Effective` |
| `CRANK_BOOTSTRAP_ADMIN_EMAIL` | `admin.bootstrap.email` | `AdminApi` | `string/-` | `required/blank` | `-` | `Internal` | `Effective` |
| `CRANK_BOOTSTRAP_ADMIN_PASSWORD` | `admin.bootstrap.password` | `AdminApi` | `secret/-` | `required/blank` | `-` | `Secret` | `Effective` |
| `CRANK_BOOTSTRAP_ADMIN_PASSWORD` | `admin.bootstrap.password` | `AdminApi` | `secret/-` | `blank` | `-` | `Secret` | `Effective` |
| `CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME` | `admin.bootstrap.display_name` | `AdminApi` | `string/-` | `Crank Owner` | `-` | `Internal` | `Effective` |
| `CRANK_DEMO_SEED` | `admin.demo_seed` | `AdminApi` | `bool/-` | `false` | `-` | `Public` | `Effective` |
| `CRANK_MCP_BIND` | `mcp.bind` | `McpServer` | `socket/-` | `0.0.0.0:3002` | `-` | `Internal` | `Effective` |
@@ -110,24 +112,58 @@ CRANK_PUBLISH_BIND=127.0.0.1
```env
CRANK_PUBLISH_BIND=0.0.0.0
CRANK_TRUSTED_PROXY_IPS=192.0.2.10
```
`CRANK_TRUSTED_PROXY_IPS` — comma-separated allowlist immediate peer IPs.
Только эти peers могут задавать `X-Real-IP`/`X-Forwarded-For`; от всех
остальных клиентов forwarding headers игнорируются. Старый
`CRANK_TRUST_FORWARDED_HEADERS` оставлен только как deprecated/no-effect
совместимость и не включает доверие к proxy.
## Авторизация администратора
- `CRANK_SESSION_SECRET` - ключ подписи браузерных сессий.
- `CRANK_PASSWORD_PEPPER` - дополнительный секрет для хэширования паролей.
- `CRANK_SESSION_TTL_HOURS` - срок жизни сессии в часах.
- `CRANK_BOOTSTRAP_ADMIN_EMAIL` - email первого пользователя.
- `CRANK_BOOTSTRAP_ADMIN_PASSWORD` - пароль первого пользователя.
- `CRANK_BOOTSTRAP_ADMIN_EMAIL` - email для локального bootstrap-контракта первого пользователя.
- `CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME` - отображаемое имя первого пользователя.
- `CRANK_BOOTSTRAP_ADMIN_PASSWORD` - deprecated compatibility-поле для старых dev/demo запусков; production startup не должен создавать или обновлять администратора из этого значения.
Первый пользователь создается или обновляется при старте `admin-api`.
Первый production-admin создается локальной операторской командой, а не
статическим startup-паролем:
```bash
crank-migrate admin-auth bootstrap-create --email owner@example.local
```
Команда выводит одноразовый bootstrap token. Оператор вводит его на странице
логина и задает первый пароль. Повторное использование token отклоняется.
## Шифрование секретов
- `CRANK_MASTER_KEY` - ключ шифрования сохраненных секретов.
- `CRANK_MASTER_KEY` - ключ шифрования сохраненных секретов. Минимум 32 bytes;
генерируйте случайное значение и храните его вне product backup set.
Этот ключ нужен `admin-api` и `mcp-server`. Если изменить ключ без миграции данных, ранее сохраненные секреты нельзя будет расшифровать.
Этот ключ нужен `admin-api` и `mcp-server`. Оба процесса до readiness
сравнивают non-secret fingerprint/epoch с PostgreSQL identity registry. Если
ключ не совпадает с активной identity, startup завершается bounded diagnostic
`master_key_identity_mismatch` без вывода raw key, fingerprint fragment,
ciphertext или decrypt details.
Не меняйте значение `CRANK_MASTER_KEY` напрямую. Используйте операторскую
процедуру:
```bash
crank-migrate master-key preflight --current-key-file /secure/current.key --target-key-file /secure/target.key
crank-migrate master-key rotate --current-key-file /secure/current.key --target-key-file /secure/target.key
crank-migrate master-key verify --target-key-file /secure/target.key
crank-migrate master-key promote --target-key-file /secure/target.key
```
Команда читает database config через `CRANK_DATABASE_URL`/`POSTGRES_*`, а
master keys — только из указанных локальных файлов. Значения ключей не должны
передаваться как shell arguments или попадать в committed `.env`.
## Демо-данные
@@ -165,6 +201,8 @@ HTTP-перенаправления и системный прокси откл
укажите его имя или IP явно. Поддерживаются маски вида `*.example.internal`.
- `CRANK_OUTBOUND_DENIED_HOSTS` - список узлов, запрещённых независимо от списка
разрешённых.
- `CRANK_OUTBOUND_MAX_REQUEST_BYTES` - максимальный размер JSON-тела запроса к
внешнему API; проверяется до отправки байтов.
- `CRANK_OUTBOUND_MAX_RESPONSE_BYTES` - максимальный размер ответа внешнего API;
по умолчанию `4194304` байт.
@@ -173,6 +211,7 @@ HTTP-перенаправления и системный прокси откл
```env
CRANK_OUTBOUND_ALLOWED_HOSTS=crm.example.internal,192.168.1.50
CRANK_OUTBOUND_DENIED_HOSTS=metadata.example.internal
CRANK_OUTBOUND_MAX_REQUEST_BYTES=4194304
CRANK_OUTBOUND_MAX_RESPONSE_BYTES=4194304
```