config: simplify runtime environment model
This commit is contained in:
+3
-5
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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 запуска проект должен поставляться с:
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user