config: simplify runtime environment model

This commit is contained in:
a.tolmachev
2026-04-11 12:40:00 +03:00
parent 9b6ce337e6
commit 51cf7691be
14 changed files with 127 additions and 66 deletions
+3 -5
View File
@@ -1,17 +1,16 @@
POSTGRES_DB=crank
POSTGRES_USER=crank
POSTGRES_PASSWORD=change-me
CRANK_DATABASE_URL=postgres://crank:change-me@postgres:5432/crank
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
CRANK_ADMIN_API_IMAGE=crank/admin-api:dev
CRANK_MCP_SERVER_IMAGE=crank/mcp-server:dev
CRANK_UI_IMAGE=crank/ui:dev
CRANK_STORAGE_ROOT=/var/lib/crank/storage
CRANK_PUBLISH_HOST=127.0.0.1
CRANK_ADMIN_BIND=0.0.0.0:3001
CRANK_MCP_BIND=0.0.0.0:3002
CRANK_MCP_REFRESH_MS=5000
CRANK_LOG_LEVEL=info
CRANK_SECRET_PROVIDER=env
CRANK_MASTER_KEY=change-me-master-key
CRANK_SESSION_SECRET=change-me-session-secret
CRANK_PASSWORD_PEPPER=change-me-password-pepper
@@ -20,5 +19,4 @@ CRANK_BOOTSTRAP_ADMIN_EMAIL=owner@crank.local
CRANK_BOOTSTRAP_ADMIN_PASSWORD=change-me-admin-password
CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME=Crank Owner
CRANK_DEMO_SEED=false
CRANK_PUBLIC_BASE_URL=https://crank.example.com
CRANK_MCP_PUBLIC_URL=https://crank.example.com/mcp
CRANK_BASE_URL=https://crank.example.com
+12 -12
View File
@@ -122,17 +122,15 @@ jobs:
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
CRANK_DATABASE_URL: ${{ secrets.CRANK_DATABASE_URL }}
POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }}
POSTGRES_PORT: ${{ secrets.POSTGRES_PORT }}
CRANK_STORAGE_ROOT: ${{ secrets.CRANK_STORAGE_ROOT }}
CRANK_PUBLISH_HOST: ${{ secrets.CRANK_PUBLISH_HOST }}
CRANK_ADMIN_BIND: ${{ secrets.CRANK_ADMIN_BIND }}
CRANK_MCP_BIND: ${{ secrets.CRANK_MCP_BIND }}
CRANK_MCP_REFRESH_MS: ${{ secrets.CRANK_MCP_REFRESH_MS }}
CRANK_LOG_LEVEL: ${{ secrets.CRANK_LOG_LEVEL }}
CRANK_SECRET_PROVIDER: ${{ secrets.CRANK_SECRET_PROVIDER }}
CRANK_MASTER_KEY: ${{ secrets.CRANK_MASTER_KEY }}
CRANK_PUBLIC_BASE_URL: ${{ secrets.CRANK_PUBLIC_BASE_URL }}
CRANK_MCP_PUBLIC_URL: ${{ secrets.CRANK_MCP_PUBLIC_URL }}
CRANK_BASE_URL: ${{ secrets.CRANK_BASE_URL }}
CRANK_SESSION_SECRET: ${{ secrets.CRANK_SESSION_SECRET }}
CRANK_PASSWORD_PEPPER: ${{ secrets.CRANK_PASSWORD_PEPPER }}
CRANK_SESSION_TTL_HOURS: ${{ secrets.CRANK_SESSION_TTL_HOURS }}
@@ -151,17 +149,15 @@ jobs:
append_if_set POSTGRES_DB "$POSTGRES_DB"
append_if_set POSTGRES_USER "$POSTGRES_USER"
append_if_set POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
append_if_set CRANK_DATABASE_URL "$CRANK_DATABASE_URL"
append_if_set POSTGRES_HOST "$POSTGRES_HOST"
append_if_set POSTGRES_PORT "$POSTGRES_PORT"
append_if_set CRANK_STORAGE_ROOT "$CRANK_STORAGE_ROOT"
append_if_set CRANK_PUBLISH_HOST "$CRANK_PUBLISH_HOST"
append_if_set CRANK_ADMIN_BIND "$CRANK_ADMIN_BIND"
append_if_set CRANK_MCP_BIND "$CRANK_MCP_BIND"
append_if_set CRANK_MCP_REFRESH_MS "$CRANK_MCP_REFRESH_MS"
append_if_set CRANK_LOG_LEVEL "$CRANK_LOG_LEVEL"
append_if_set CRANK_SECRET_PROVIDER "$CRANK_SECRET_PROVIDER"
append_if_set CRANK_MASTER_KEY "$CRANK_MASTER_KEY"
append_if_set CRANK_PUBLIC_BASE_URL "$CRANK_PUBLIC_BASE_URL"
append_if_set CRANK_MCP_PUBLIC_URL "$CRANK_MCP_PUBLIC_URL"
append_if_set CRANK_BASE_URL "$CRANK_BASE_URL"
append_if_set CRANK_SESSION_SECRET "$CRANK_SESSION_SECRET"
append_if_set CRANK_PASSWORD_PEPPER "$CRANK_PASSWORD_PEPPER"
append_if_set CRANK_SESSION_TTL_HOURS "$CRANK_SESSION_TTL_HOURS"
@@ -184,13 +180,17 @@ jobs:
set -e
cd '${{ secrets.DEPLOY_PATH }}'
required_vars='
CRANK_DATABASE_URL
POSTGRES_HOST
POSTGRES_PORT
POSTGRES_DB
POSTGRES_USER
POSTGRES_PASSWORD
CRANK_MASTER_KEY
CRANK_SESSION_SECRET
CRANK_PASSWORD_PEPPER
CRANK_BOOTSTRAP_ADMIN_EMAIL
CRANK_BOOTSTRAP_ADMIN_PASSWORD
CRANK_PUBLIC_BASE_URL
CRANK_BASE_URL
'
for var in \$required_vars; do
value=\$(grep -E \"^\${var}=\" .env | tail -n1 | cut -d= -f2- || true)
+5 -5
View File
@@ -2,15 +2,15 @@
## Current
### `feat/rust-docker-build-cache`
### `feat/runtime-env-simplification`
Status: in_progress
DoD:
- Rust Docker builds use a cache-friendly multi-stage layout
- `admin-api` and `mcp-server` reuse dependency layers across rebuilds
- noisy local artifacts are excluded from Docker build context
- deployment fails early when required secret-store env is missing
- runtime uses component-based PostgreSQL config as the primary model
- `CRANK_BASE_URL` replaces split public URL variables
- deployment uses one GitHub secret per runtime env variable
- deprecated env variables are removed from compose, examples, and docs
## Next
+1 -1
View File
@@ -23,6 +23,7 @@ serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
sha2.workspace = true
sqlx.workspace = true
thiserror.workspace = true
time.workspace = true
tokio = { workspace = true, features = ["fs"] }
@@ -33,4 +34,3 @@ uuid.workspace = true
[dev-dependencies]
reqwest.workspace = true
serial_test = "3"
sqlx.workspace = true
+26 -5
View File
@@ -9,6 +9,7 @@ mod storage;
use std::{env, net::SocketAddr, path::PathBuf};
use crank_registry::PostgresRegistry;
use sqlx::postgres::PgConnectOptions;
use tokio::net::TcpListener;
use tracing::info;
@@ -28,15 +29,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
)
.init();
let database_url = env::var("CRANK_DATABASE_URL")?;
let storage_root = PathBuf::from(
env::var("CRANK_STORAGE_ROOT").unwrap_or_else(|_| "/var/lib/crank/storage".into()),
);
let bind_addr = env::var("CRANK_ADMIN_BIND").unwrap_or_else(|_| "0.0.0.0:3001".into());
let public_base_url =
env::var("CRANK_PUBLIC_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".into());
let base_url = env::var("CRANK_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".into());
let socket_addr: SocketAddr = bind_addr.parse()?;
let registry = PostgresRegistry::connect(&database_url).await?;
let registry = PostgresRegistry::connect_with_options(database_options_from_env()?).await?;
let auth_settings = AuthSettings {
session_secret: env::var("CRANK_SESSION_SECRET")?,
password_pepper: env::var("CRANK_PASSWORD_PEPPER")?,
@@ -44,7 +43,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.ok()
.and_then(|value| value.parse::<i64>().ok())
.unwrap_or(24),
cookie_secure: public_base_url.starts_with("https://"),
cookie_secure: base_url.starts_with("https://"),
bootstrap_admin: BootstrapAdminConfig {
email: env::var("CRANK_BOOTSTRAP_ADMIN_EMAIL")?,
password: env::var("CRANK_BOOTSTRAP_ADMIN_PASSWORD")?,
@@ -79,3 +78,25 @@ fn env_flag(name: &str) -> bool {
Some("1" | "true" | "yes" | "on")
)
}
fn database_options_from_env() -> Result<PgConnectOptions, Box<dyn std::error::Error>> {
if let Ok(database_url) = env::var("CRANK_DATABASE_URL") {
return Ok(database_url.parse::<PgConnectOptions>()?);
}
let host = env::var("POSTGRES_HOST").unwrap_or_else(|_| "postgres".into());
let port = env::var("POSTGRES_PORT")
.ok()
.and_then(|value| value.parse::<u16>().ok())
.unwrap_or(5432);
let database = env::var("POSTGRES_DB").unwrap_or_else(|_| "crank".into());
let username = env::var("POSTGRES_USER").unwrap_or_else(|_| "crank".into());
let password = env::var("POSTGRES_PASSWORD").unwrap_or_else(|_| "crank".into());
Ok(PgConnectOptions::new()
.host(&host)
.port(port)
.database(&database)
.username(&username)
.password(&password))
}
+1 -1
View File
@@ -16,6 +16,7 @@ futures-util = "0.3"
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
sqlx.workspace = true
time.workspace = true
tokio = { workspace = true, features = ["sync"] }
tracing.workspace = true
@@ -27,4 +28,3 @@ crank-adapter-grpc = { path = "../../crates/crank-adapter-grpc", features = ["te
crank-mapping = { path = "../../crates/crank-mapping" }
crank-schema = { path = "../../crates/crank-schema" }
reqwest.workspace = true
sqlx.workspace = true
+26 -4
View File
@@ -7,6 +7,7 @@ use std::{env, net::SocketAddr, time::Duration};
use crank_registry::PostgresRegistry;
use crank_runtime::SecretCrypto;
use sqlx::postgres::PgConnectOptions;
use tokio::net::TcpListener;
use tracing::info;
@@ -21,18 +22,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
)
.init();
let database_url = env::var("CRANK_DATABASE_URL")?;
let bind_addr = env::var("CRANK_MCP_BIND").unwrap_or_else(|_| "0.0.0.0:3002".into());
let public_base_url = env::var("CRANK_PUBLIC_BASE_URL").ok();
let base_url = env::var("CRANK_BASE_URL").ok();
let refresh_interval = env::var("CRANK_MCP_REFRESH_MS")
.ok()
.and_then(|value| value.parse::<u64>().ok())
.map(Duration::from_millis)
.unwrap_or_else(|| Duration::from_secs(5));
let socket_addr: SocketAddr = bind_addr.parse()?;
let registry = PostgresRegistry::connect(&database_url).await?;
let registry = PostgresRegistry::connect_with_options(database_options_from_env()?).await?;
let secret_crypto = SecretCrypto::new(&env::var("CRANK_MASTER_KEY")?)?;
let app = build_app(registry, refresh_interval, public_base_url, secret_crypto);
let app = build_app(registry, refresh_interval, base_url, secret_crypto);
let listener = TcpListener::bind(socket_addr).await?;
info!("mcp-server listening on {}", socket_addr);
@@ -42,6 +42,28 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn database_options_from_env() -> Result<PgConnectOptions, Box<dyn std::error::Error>> {
if let Ok(database_url) = env::var("CRANK_DATABASE_URL") {
return Ok(database_url.parse::<PgConnectOptions>()?);
}
let host = env::var("POSTGRES_HOST").unwrap_or_else(|_| "postgres".into());
let port = env::var("POSTGRES_PORT")
.ok()
.and_then(|value| value.parse::<u16>().ok())
.unwrap_or(5432);
let database = env::var("POSTGRES_DB").unwrap_or_else(|_| "crank".into());
let username = env::var("POSTGRES_USER").unwrap_or_else(|_| "crank".into());
let password = env::var("POSTGRES_PASSWORD").unwrap_or_else(|_| "crank".into());
Ok(PgConnectOptions::new()
.host(&host)
.port(port)
.database(&database)
.username(&username)
.password(&password))
}
#[cfg(test)]
mod tests {
use std::{
+6 -4
View File
@@ -91,13 +91,16 @@ until docker exec "$POSTGRES_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POST
sleep 1
done
export CRANK_DATABASE_URL="postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:$POSTGRES_PORT/$POSTGRES_DB"
export POSTGRES_HOST="127.0.0.1"
export POSTGRES_PORT="$POSTGRES_PORT"
export POSTGRES_DB="$POSTGRES_DB"
export POSTGRES_USER="$POSTGRES_USER"
export POSTGRES_PASSWORD="$POSTGRES_PASSWORD"
export CRANK_STORAGE_ROOT="$TMP_DIR/storage"
export CRANK_ADMIN_BIND="0.0.0.0:$ADMIN_PORT"
export CRANK_MCP_BIND="0.0.0.0:$MCP_PORT"
export CRANK_MCP_REFRESH_MS="1000"
export CRANK_LOG_LEVEL="info"
export CRANK_SECRET_PROVIDER="env"
export CRANK_SESSION_SECRET="e2e-session-secret"
export CRANK_PASSWORD_PEPPER="e2e-password-pepper"
export CRANK_MASTER_KEY="0000000000000000000000000000000000000000000000000000000000000000"
@@ -106,8 +109,7 @@ export CRANK_BOOTSTRAP_ADMIN_EMAIL="$ADMIN_EMAIL"
export CRANK_BOOTSTRAP_ADMIN_PASSWORD="$ADMIN_PASSWORD"
export CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME="Crank E2E"
export CRANK_DEMO_SEED="true"
export CRANK_PUBLIC_BASE_URL="http://127.0.0.1:$UI_PORT"
export CRANK_MCP_PUBLIC_URL="http://127.0.0.1:$MCP_PORT"
export CRANK_BASE_URL="http://127.0.0.1:$UI_PORT"
export CRANK_E2E_STREAM_FIXTURE_PORT="$STREAM_FIXTURE_PORT"
export CRANK_E2E_GRPC_FIXTURE_BIND="$GRPC_FIXTURE_BIND"
+9 -1
View File
@@ -10,7 +10,7 @@ use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value;
use sqlx::{
PgPool, Postgres, Row, Transaction,
postgres::{PgPoolOptions, PgRow},
postgres::{PgConnectOptions, PgPoolOptions, PgRow},
types::Json,
};
@@ -46,6 +46,14 @@ impl PostgresRegistry {
Self::connect_in_schema(database_url, None).await
}
pub async fn connect_with_options(
connect_options: PgConnectOptions,
) -> Result<Self, RegistryError> {
let pool = PgPoolOptions::new().connect_with(connect_options).await?;
migrations::apply_postgres(&pool).await?;
Ok(Self { pool })
}
pub fn pool(&self) -> &PgPool {
&self.pool
}
+15 -9
View File
@@ -23,13 +23,16 @@ services:
dockerfile: apps/admin-api/Dockerfile
restart: unless-stopped
environment:
CRANK_DATABASE_URL: ${CRANK_DATABASE_URL:-postgres://crank:crank@postgres:5432/crank}
POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
POSTGRES_DB: ${POSTGRES_DB:-crank}
POSTGRES_USER: ${POSTGRES_USER:-crank}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-crank}
CRANK_STORAGE_ROOT: ${CRANK_STORAGE_ROOT:-/var/lib/crank/storage}
CRANK_ADMIN_BIND: ${CRANK_ADMIN_BIND:-0.0.0.0:3001}
CRANK_LOG_LEVEL: ${CRANK_LOG_LEVEL:-info}
CRANK_SECRET_PROVIDER: ${CRANK_SECRET_PROVIDER:-env}
CRANK_MASTER_KEY: ${CRANK_MASTER_KEY}
CRANK_PUBLIC_BASE_URL: ${CRANK_PUBLIC_BASE_URL:-http://localhost:3000}
CRANK_BASE_URL: ${CRANK_BASE_URL:-http://localhost:3000}
CRANK_SESSION_SECRET: ${CRANK_SESSION_SECRET}
CRANK_PASSWORD_PEPPER: ${CRANK_PASSWORD_PEPPER}
CRANK_SESSION_TTL_HOURS: ${CRANK_SESSION_TTL_HOURS:-24}
@@ -43,7 +46,7 @@ services:
volumes:
- artifact_storage:${CRANK_STORAGE_ROOT:-/var/lib/crank/storage}
ports:
- "${CRANK_PUBLISH_HOST:-127.0.0.1}:3001:3001"
- "127.0.0.1:3001:3001"
healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:3001/health"]
interval: 15s
@@ -57,21 +60,24 @@ services:
dockerfile: apps/mcp-server/Dockerfile
restart: unless-stopped
environment:
CRANK_DATABASE_URL: ${CRANK_DATABASE_URL:-postgres://crank:crank@postgres:5432/crank}
POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
POSTGRES_DB: ${POSTGRES_DB:-crank}
POSTGRES_USER: ${POSTGRES_USER:-crank}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-crank}
CRANK_STORAGE_ROOT: ${CRANK_STORAGE_ROOT:-/var/lib/crank/storage}
CRANK_MCP_BIND: ${CRANK_MCP_BIND:-0.0.0.0:3002}
CRANK_MCP_REFRESH_MS: ${CRANK_MCP_REFRESH_MS:-5000}
CRANK_LOG_LEVEL: ${CRANK_LOG_LEVEL:-info}
CRANK_SECRET_PROVIDER: ${CRANK_SECRET_PROVIDER:-env}
CRANK_MASTER_KEY: ${CRANK_MASTER_KEY}
CRANK_PUBLIC_BASE_URL: ${CRANK_PUBLIC_BASE_URL:-http://localhost:3000}
CRANK_BASE_URL: ${CRANK_BASE_URL:-http://localhost:3000}
depends_on:
postgres:
condition: service_healthy
volumes:
- artifact_storage:${CRANK_STORAGE_ROOT:-/var/lib/crank/storage}
ports:
- "${CRANK_PUBLISH_HOST:-127.0.0.1}:3002:3002"
- "127.0.0.1:3002:3002"
healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:3002/health"]
interval: 15s
@@ -85,7 +91,7 @@ services:
dockerfile: apps/ui/Dockerfile
restart: unless-stopped
ports:
- "${CRANK_PUBLISH_HOST:-127.0.0.1}:3000:3000"
- "127.0.0.1:3000:3000"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/"]
interval: 15s
+1 -1
View File
@@ -7,7 +7,7 @@
## 2. Предусловия
- доступен `PostgreSQL`;
- задан `CRANK_DATABASE_URL`;
- заданы `POSTGRES_HOST`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`;
- задан `CRANK_STORAGE_ROOT`;
- доступны `CRANK_ADMIN_BIND` и `CRANK_MCP_BIND`;
- если нужен заполненный demo state без ручного онбординга, задан `CRANK_DEMO_SEED=true`;
+4 -13
View File
@@ -165,17 +165,10 @@ services:
- поднимать `postgres`, `admin-api`, `mcp-server`, `ui`;
- использовать env vars вместо hardcoded config;
- иметь persistent storage для БД и artifacts;
- поддерживать настраиваемый bind host для published ports;
- публиковать `ui`, `admin-api` и `mcp-server` на loopback, если reverse proxy живет на том же хосте;
- публиковать `ui`, `admin-api` и `mcp-server` на LAN bind address или `0.0.0.0`, если reverse proxy вынесен на отдельный хост;
- публиковать `ui`, `admin-api` и `mcp-server` на loopback по умолчанию;
- не публиковать внутренние сервисы наружу без необходимости;
- содержать healthchecks.
Практически это должно быть управляемо через env вроде `CRANK_PUBLISH_HOST`:
- `127.0.0.1` для local reverse proxy на том же Linux host;
- конкретный внутренний IP или `0.0.0.0` для отдельного центрального reverse proxy в LAN.
## 10. Healthchecks
Нужны как минимум:
@@ -238,20 +231,18 @@ CD для MVP должен:
Workflow собирает `.env` на сервере из отдельных GitHub secrets. Для runtime-переменных нужно
создавать secrets с теми же именами, что и у приложений:
- `POSTGRES_HOST`
- `POSTGRES_PORT`
- `POSTGRES_DB`
- `POSTGRES_USER`
- `POSTGRES_PASSWORD`
- `CRANK_DATABASE_URL`
- `CRANK_STORAGE_ROOT`
- `CRANK_PUBLISH_HOST`
- `CRANK_ADMIN_BIND`
- `CRANK_MCP_BIND`
- `CRANK_MCP_REFRESH_MS`
- `CRANK_LOG_LEVEL`
- `CRANK_SECRET_PROVIDER`
- `CRANK_MASTER_KEY`
- `CRANK_PUBLIC_BASE_URL`
- `CRANK_MCP_PUBLIC_URL`
- `CRANK_BASE_URL`
- `CRANK_SESSION_SECRET`
- `CRANK_PASSWORD_PEPPER`
- `CRANK_SESSION_TTL_HOURS`
+17 -4
View File
@@ -55,7 +55,11 @@ var/crank/
Минимально ожидаются:
- `CRANK_DATABASE_URL`
- `POSTGRES_HOST`
- `POSTGRES_PORT`
- `POSTGRES_DB`
- `POSTGRES_USER`
- `POSTGRES_PASSWORD`
- `CRANK_ADMIN_API_IMAGE`
- `CRANK_MCP_SERVER_IMAGE`
- `CRANK_UI_IMAGE`
@@ -64,10 +68,8 @@ var/crank/
- `CRANK_MCP_BIND`
- `CRANK_MCP_REFRESH_MS`
- `CRANK_LOG_LEVEL`
- `CRANK_SECRET_PROVIDER`
- `CRANK_MASTER_KEY`
- `CRANK_PUBLIC_BASE_URL`
- `CRANK_MCP_PUBLIC_URL`
- `CRANK_BASE_URL`
Опционально:
@@ -151,6 +153,17 @@ runtime env-переменной. Это значит, что:
- `CRANK_DEMO_SEED` хранится как GitHub secret `CRANK_DEMO_SEED`;
- и так же для остальных runtime-переменных.
Для БД основной runtime-контракт теперь компонентный:
- `POSTGRES_HOST`
- `POSTGRES_PORT`
- `POSTGRES_DB`
- `POSTGRES_USER`
- `POSTGRES_PASSWORD`
`CRANK_DATABASE_URL` допускается только как backward-compatible fallback для локальных тестов и
переходного периода, но не как основная deployment-модель.
## 8.1. Delivery artifacts
Для production-like запуска проект должен поставляться с:
+1 -1
View File
@@ -14,7 +14,7 @@
- `auth_profile_ref` в `ExecutionConfig`;
- CRUD endpoints для `auth_profiles`;
- UI поля для upstream auth headers;
- env `CRANK_SECRET_PROVIDER` и `CRANK_MASTER_KEY` в runtime docs.
- env `CRANK_MASTER_KEY` в runtime docs.
Что еще не работает end-to-end: