Files
civitai__civitai/.env-example
T
Zachary Lowden 65bfb7ece8 feat(app-blocks): make the cap-limit degrade path observable + rename the absolute-ceiling env knobs (#3528)
Two follow-ups to #3519 (per-app generation spend/velocity caps).

1) OBSERVABILITY of the degrade-to-strictest path.

`resolveAppCapLimits` falls back to STRICTEST_APP_CAP_LIMITS on a DB error
or a missing `app_blocks` row. That behaviour is right — never uncapped, and
a hard deny would turn a transient DB blip into a full generation outage —
but it was SILENT, and an app pinned to the strictest ceiling looks exactly
like an app that is merely busy. The first symptom would be that app's users
hitting abuse rejections they did not earn. (This is the same silent-
degradation shape as #3520, which is why it is worth closing here.)

Adds `civitai_app_block_cap_limits_degraded_total{reason}` —
`db_error` (the read threw: infra; every app degrades at once, page-worthy)
vs `missing_row` (the read succeeded and there is no such app: one app,
points at an id-minting bug) — plus a paired `console.warn` carrying the
specific appBlockId. Follows the existing convention in
src/server/metrics/app-block-runtime.metrics.ts (get-or-create against the
default registry + a fail-soft emit wrapper); no new mechanism.

- NO `app_block_id` prom label, deliberately. `missing_row` fires precisely
  for ids absent from the app catalog, i.e. the unbounded population
  known-app-blocks.service.ts exists to clamp, and prom-client retains every
  distinct label set in the heap forever. The usual clamp needs a DB read —
  the very thing broken on the `db_error` path. So: alert on the metric,
  attribute from the log.
- NOT a failure path. The metric emit and the log are independently guarded
  and `recordAppCapLimitsDegrade` is total on its own side, so neither a
  broken registry nor a throwing console can perturb cap resolution.
- NOT on the hot path. Only a cache MISS that DEGRADED emits; a warm hit and
  a miss that resolves a real row never reach it. Volume is bounded by the
  5s fallback-TTL cache, not by submit rate — a 10k-submit burst against one
  degraded app emits once.

2) RENAME the two absolute-ceiling env knobs.

`BLOCK_APP_SPEND_CAP_BUZZ_PER_DAY` / `BLOCK_APP_SPEND_VELOCITY_MAX_GENS`
used to BE the ceilings. Since #3519 they are absolute bounds that clamp the
tier table AND any per-app moderator override, so an operator reaching for
`..._VELOCITY_MAX_GENS` mid-incident would reasonably read it as "set the
limit" rather than "bound it".

  BLOCK_APP_SPEND_CAP_BUZZ_PER_DAY   -> BLOCK_APP_SPEND_ABSOLUTE_MAX_BUZZ_PER_DAY
  BLOCK_APP_SPEND_VELOCITY_MAX_GENS  -> BLOCK_APP_SPEND_ABSOLUTE_MAX_GENS_PER_WINDOW

The legacy names are still honoured (dp-prod sets neither, but other
environments are not enumerable from here, and silently ignoring a set spend
guardrail is unacceptable), with a deprecation warning that also states the
changed meaning. A valid new value always wins; a set-but-ignored or
set-but-unusable legacy value warns too. Exported symbols follow the env
names, with the old export names kept as deprecated aliases so pre-rename
importers keep compiling.

Tests: 41 new across two files. Every new guard was mutation-verified —
14 mutations, each killing a specific named test.
2026-08-01 18:37:27 -05:00

213 lines
7.0 KiB
Plaintext

# Since .env is gitignored, you can use .env-example to build a new `.env` file when you clone the repo.
# Keep this file up-to-date when you add new variables to `.env`.
# This file will be committed to version control, so make sure not to have any secrets in it.
# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets.
# When adding additional env variables, the schema in /env/schema.mjs should be updated accordingly
# The default values for Prisma, Redis, S3, and Email are set to work with the docker-compose setup
# Database
DATABASE_SSL=false
DATABASE_URL=postgresql://postgres:postgres@localhost:15432/civitai
DATABASE_REPLICA_URL=postgresql://postgres:postgres@localhost:15432/civitai
NOTIFICATION_DB_URL=postgresql://postgres:postgres@localhost:15434/postgres
NOTIFICATION_DB_REPLICA_URL=postgresql://postgres:postgres@localhost:15434/postgres
DATAPACKET_DATABASE_RO_URL=postgresql://postgres:postgres@localhost:15435/postgres
# Redis
REDIS_URL=redis://:redis@localhost:6379
REDIS_SYS_URL=redis://:redis@localhost:6378
# Optional: switch the `system` redis client to Sentinel mode. When set, REDIS_SYS_URL
# is still parsed for credentials but the connection is established via Sentinel.
# REDIS_SYS_SENTINELS=localhost:26379,localhost:26380,localhost:26381
# REDIS_SYS_SENTINEL_NAME=sysmaster # production uses "sysmaster"; match the master group your Sentinel CR declares
# Logging
LOGGING=prisma:error,prisma:warn,seed-metrics-search
# Next Auth
NEXTAUTH_SECRET=thisisnotasecret
NEXTAUTH_URL=http://localhost:3000
# Next Auth Discord Provider
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
# Next Auth GitHub Provider
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# Next Auth Google Provider
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Next Auth Reddit Provider
REDDIT_CLIENT_ID=
REDDIT_CLIENT_SECRET=
# Integrations
DISCORD_BOT_TOKEN=
DISCORD_GUILD_ID=
DISCORD_WEBHOOK_MOD_ALERTS=
# File uploading
S3_UPLOAD_KEY=REFER_TO_README
S3_UPLOAD_SECRET=REFER_TO_README
S3_UPLOAD_BUCKET=modelshare
S3_UPLOAD_REGION=us-east-1
S3_UPLOAD_ENDPOINT=http://127.0.0.1:9000
# Image uploading
S3_IMAGE_UPLOAD_KEY=
S3_IMAGE_UPLOAD_SECRET=
S3_IMAGE_UPLOAD_BUCKET=images
S3_IMAGE_UPLOAD_REGION=us-east-1
S3_IMAGE_UPLOAD_ENDPOINT=http://127.0.0.1:9000
S3_IMAGE_CACHE_BUCKET=cache
S3_IMAGE_UPLOAD_OVERRIDE=
# Client env vars
NEXT_PUBLIC_IMAGE_LOCATION=http://localhost:3000
NEXT_PUBLIC_CONTENT_DECTECTION_LOCATION=https://publicstore.civitai.com/content_detection/model.json
NEXT_PUBLIC_CIVITAI_LINK=http://localhost:3000
NEXT_PUBLIC_UI_CATEGORY_VIEWS=false
NEXT_PUBLIC_UI_HOMEPAGE_IMAGES=false
NEXT_PUBLIC_ADS=true
# Clickhouse
CLICKHOUSE_HOST=http://localhost:18123
CLICKHOUSE_USERNAME=default
CLICKHOUSE_PASSWORD=
CLICKHOUSE_TRACKER_URL=http://localhost:3000
# Email
EMAIL_HOST=localhost
EMAIL_PORT=1025
EMAIL_USER=
EMAIL_PASS=
EMAIL_FROM=
# Endpoint Protection
JOB_TOKEN=thisisnotatoken
WEBHOOK_TOKEN=thisisnotatoken
# Site Configuration
UNAUTHENTICATED_DOWNLOAD=true
UNAUTHENTICATED_LIST_NSFW=false
SHOW_SFW_IN_NSFW=false
MAINTENANCE_MODE=false
RATE_LIMITING=true
TRPC_ORIGINS=
# Security
SCANNING_ENDPOINT=http://scan-me.civitai.com/enqueue
SCANNING_TOKEN=thisisnotatoken
# Delivery worker
DELIVERY_WORKER_ENDPOINT=https://delivery-worker.civitai.com/download
DELIVERY_WORKER_TOKEN=thisisnotatoken
# Payments
PADDLE_SECRET_KEY=thisisnotasecret
PADDLE_WEBHOOK_SECRET=thisisnotasecret
NEXT_PUBLIC_PADDLE_TOKEN=thisisnotatoken
NEXT_PUBLIC_DEFAULT_PAYMENT_PROVIDER=Paddle
# Features
FEATURE_FLAG_EARLY_ACCESS_MODEL=public
# MeiliSearch
SEARCH_HOST=http://localhost:7700
SEARCH_API_KEY=meilisearch
NEXT_PUBLIC_SEARCH_HOST=http://localhost:7700
NEXT_PUBLIC_SEARCH_CLIENT_KEY=meilisearch
METRICS_SEARCH_HOST=http://localhost:7700
METRICS_SEARCH_API_KEY=meilisearch
# Debounce window (ms) for flushing model-metric-affected ids into the model
# search-index update queue. Widening it collapses more of a hot model's repeated
# metric changes into a single reindex (cost: metric/popularity staleness lags by
# up to the window). Fail-soft: a bad value falls back to the 45m default. Requires
# a restart/rollout to take effect. Default 45m.
# SEARCH_INDEX_MODEL_METRIC_FLUSH_INTERVAL_MS=2700000
# Per-call Meilisearch timeout in ms. Calls wrapped via withMeili() fail fast
# with MeiliCallTimeoutError once exceeded, instead of hanging until Traefik's
# 30s router timeout fires.
MEILI_CALL_TIMEOUT_MS=2500
# Per-pod cap on in-flight Meilisearch calls wrapped via withMeili(). Excess
# calls fail fast with MeiliCallTimeoutError instead of queueing forever.
MEILI_CALL_CONCURRENCY=50
# Per-backend circuit breaker. If MEILI_CIRCUIT_TRIP_THRESHOLD wrapped-call
# timeouts accumulate within MEILI_CIRCUIT_WINDOW_SECONDS on a backend, the
# circuit OPENs and all calls fail at 0ms for MEILI_CIRCUIT_COOLDOWN_SECONDS,
# then HALF_OPEN issues a single trial request. healthProbe is excluded.
MEILI_CIRCUIT_TRIP_THRESHOLD=10
MEILI_CIRCUIT_WINDOW_SECONDS=30
MEILI_CIRCUIT_COOLDOWN_SECONDS=30
# BaseURL
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# Recaptcha
RECAPTCHA_PROJECT_ID=aSampleKey
NEXT_PUBLIC_RECAPTCHA_KEY=aSampleKey
# CF Turnstile
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITEKEY=1x00000000000000000000BB
CLOUDFLARE_TURNSTILE_SECRET=1x0000000000000000000000000000000AA
NEXT_PUBLIC_CF_INVISIBLE_TURNSTILE_SITEKEY=1x00000000000000000000BB
CF_INVISIBLE_TURNSTILE_SECRET=1x0000000000000000000000000000000AA
NEXT_PUBLIC_CF_MANAGED_TURNSTILE_SITEKEY=1x00000000000000000000AA
CF_MANAGED_TURNSTILE_SECRET=1x0000000000000000000000000000000AA
ORCHESTRATOR_ENDPOINT=http://localhost
ORCHESTRATOR_ACCESS_TOKEN=asdf
BUZZ_ENDPOINT=http://localhost
SIGNALS_ENDPOINT=http://localhost
NEXT_PUBLIC_SIGNALS_ENDPOINT=http://localhost
NOW_PAYMENTS_API_URL=http://localhost
NOW_PAYMENTS_API_KEY=key
NOW_PAYMENTS_IPN_KEY=key
COINBASE_API_URL=http://localhost
COINBASE_API_KEY=key
COINBASE_WEBHOOK_SECRET=secret
EMERCHANTPAY_WPF_URL=
EMERCHANTPAY_USERNAME=
EMERCHANTPAY_PASSWORD=
# Shopify merch store — Blue Buzz reward loop
# SHOPIFY_SHOP_DOMAIN = the *.myshopify.com admin domain (e.g. ff1592-5.myshopify.com)
SHOPIFY_SHOP_DOMAIN=
SHOPIFY_WEBHOOK_SECRET=
# Admin auth: client_credentials grant (preferred). Set ADMIN_TOKEN instead only for a static token.
SHOPIFY_CLIENT_ID=
SHOPIFY_CLIENT_SECRET=
SHOPIFY_ADMIN_TOKEN=
FLIPT_URL=""
FLIPT_FETCHER_SECRET=placeholder
IMAGE_SCANNER_NEW=false
# App Blocks — per-app generation spend/velocity ABSOLUTE CEILINGS (incident knobs).
# 🔴 These are UPPER BOUNDS, not the limit an app receives. Each app's actual
# ceilings come from its server-owned `spendTier` (+ any moderator per-app
# override); these clamp the tier table AND any override from above, so setting
# one TIGHTENS every app and can never loosen one. Unset = no extra clamp.
# Formerly BLOCK_APP_SPEND_CAP_BUZZ_PER_DAY / BLOCK_APP_SPEND_VELOCITY_MAX_GENS —
# those names are DEPRECATED but still honoured (with a startup warning).
# BLOCK_APP_SPEND_ABSOLUTE_MAX_BUZZ_PER_DAY=
# BLOCK_APP_SPEND_ABSOLUTE_MAX_GENS_PER_WINDOW=
# Window (seconds) the gens-per-window ceiling is measured over. Default 60.
# BLOCK_APP_SPEND_VELOCITY_WINDOW_SECONDS=