Files
civitai__civitai/.env-example
T
Justin Maier 7cff7ceb4f fix(env): read NEXT_PUBLIC_LOG_TRPC from its own variable, drop dead public env vars (#4035)
* fix(env): read NEXT_PUBLIC_LOG_TRPC from its own variable, drop dead public env

`clientEnv` restates every key by hand because Next.js inlines only the
`process.env.NEXT_PUBLIC_*` references it can see literally. `NEXT_PUBLIC_LOG_TRPC`
named `NEXT_PUBLIC_LOG_TRP`, so it was permanently undefined and the schema default
turned that into `false` with nothing to read.

`NEXT_PUBLIC_CONTENT_DECTECTION_LOCATION` has no consumers anywhere in the
workspace; removed from the schema, the Dockerfile ARG and `.env-example`. Same for
the `NEXT_PUBLIC_MAINTENANCE_MODE` ARG (no such var in either schema) and the
`NEXT_PUBLIC_UI_CATEGORY_VIEWS` / `NEXT_PUBLIC_ADS` lines in `.env-example`.

`NEXT_PUBLIC_IMAGE_LOCATION` keeps its `.default('')` deliberately. Requiring it
would fail builds that legitimately do not pass it, and the dangerous call sites
already refuse a relative URL.

The new test stamps a per-key sentinel into `process.env` and asserts each key read
its own name, so this class of typo fails naming the variable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(env): close the gaps the review found in the client-env guard

Three findings from the adversarial pass, each with the control run rather than
assumed.

The sentinel sweep exempted `NEXT_PUBLIC_DEFAULT_PAYMENT_PROVIDER` — the one key
resolved from an expression, and so the one most able to carry the typo the guard
exists to catch. Dropping the trailing `R` from the read pinned every user to Stripe
with the suite still green. Replaced the blanket skip with assertions on the value,
plus a check that nothing is exempted which is not in the schema.

Making the `NEXT_PUBLIC_LOG_TRPC` read live changes parse behaviour for anything
already setting it: `z.stringbool()` rejects the empty string, and a failed parse
throws out of `~/env/client` at import. A value that was inert before this branch
would have hard-failed after it, so the schema now catches to `false`.

`NEXT_PUBLIC_BASE_URL` falls back to `NEXTAUTH_URL`, which has no `NEXT_PUBLIC_`
prefix and is therefore never inlined into the client bundle — an environment
setting only that one resolves server-side and is `undefined` in the browser. Left
in place rather than removed, since dropping it changes behaviour beyond this
ticket, but pinned by tests so it is visible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(env): treat an empty NEXT_PUBLIC_LOG_TRPC as unset, not as a catch-all

`.catch(false)` swallowed every unparseable value, which is the same plausible-false
failure this branch exists to remove — just moved from the variable name to its
value — and made this the only stringbool in the file that hides a misconfiguration.

Only the empty case needs handling: the read named the wrong variable until this
branch, so a config carrying a valueless key had been inert and would otherwise
start throwing out of `~/env/client`. Anything else unparseable throws, as it does
for every other flag here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 15:10:06 -06:00

210 lines
6.9 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_CIVITAI_LINK=http://localhost:3000
NEXT_PUBLIC_UI_HOMEPAGE_IMAGES=false
# 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=