# SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 # Every variable this app reads, what it is for, who sets it on ConusAI Cloud, and what a local run # puts here. Nothing is read at build time, so one image serves every environment (there is no # NEXT_PUBLIC_* value in this file by design, spec §6.4). A test fails when a variable the code reads # is missing from this file (test/env-example.test.ts). # # Two ways to run it locally, and they need different things from this file: # # pnpm dev # needs NO .env at all, and reads none. It starts Postgres in Docker, the fake platform, the # migrate job, the demo data, the worker and the web shell on this machine, with the values it # prints. A value you want different is a variable in your shell, not in this file: DEV_WEB_PORT, # DEV_PLATFORM_PORT and DEV_POSTGRES_PORT move the ports; POSTGRES_URL, POSTGRES_READER_URL and # POSTGRES_MIGRATE_URL (all three) use a Postgres of your own. # # cp .env.example .env && docker compose -f compose.yaml -f compose.dev.yaml up --build # needs this file as it is, plus AUTH_SESSION_SECRET (below). The same stack as containers. # # On ConusAI Cloud the platform injects the "platform" and "Postgres" groups, and you set the # "sign-in" group and anything under "this app" in the project's settings. # --------------------------------------------------------------------------------------------- # The platform (injected into every service; @conusai-kit/platform parses it at boot and the app # refuses to start without the first four, naming the one that is missing) # --------------------------------------------------------------------------------------------- # The platform API, ending in /api. The AI gateway, OCR, Blob, audit and usage are all reached here. CONUSAI_API_URL=http://platform:4010/api # The deployment token. A secret: never in an image, a log or an error message. CONUSAI_API_TOKEN=dev-token # The project this deployment is. CONUSAI_PROJECT_ID=1 # The ConusAI organization that owns the deployment: usage is metered against it. Required in # production; a development run uses "dev". CONUSAI_ORG_ID=dev # Platform-injected organization slug for slug-scoped usage billing. CONUSAI_ORG_SLUG= # The address the app is served at, with no trailing slash. The sign-in redirect URIs derive from it. CONUSAI_PUBLIC_URL=http://127.0.0.1:3000 # The release being run (the commit), reported with every error and log line. CONUSAI_RELEASE=dev # production, preview or a named environment. Without it: production in a production run, else development. CONUSAI_ENVIRONMENT= # --------------------------------------------------------------------------------------------- # Convex profile only. The backup sidecar needs an admin key; keep this secret out of source control. # --------------------------------------------------------------------------------------------- # The Convex provider injects these exact names into linked services. The admin key is server-only: # it deploys app functions and lets the Next BFF attach trusted identities; never prefix it NEXT_PUBLIC. CONVEX_SELF_HOSTED_URL=http://127.0.0.1:53210 CONVEX_SELF_HOSTED_ADMIN_KEY= # For local development, generate the key # with `docker exec ./generate_admin_key.sh`. # Scheduled export cadence and number of newest archives retained. Defaults: 24 hours and 30. CONVEX_BACKUP_INTERVAL_HOURS=24 CONVEX_BACKUP_RETENTION=30 # Optional Convex CLI executable and working directory overrides for the backup sidecar. CONVEX_CLI= CONVEX_APP_DIR= # Development and tests ONLY: use the fake platform of @conusai-kit/testing instead of the real one. # The app refuses this switch in a production run (anything but NODE_ENV=development or test), and # refuses it when CONUSAI_API_URL is not on this machine or the compose network. `pnpm dev` sets it. CONUSAI_FAKE_PLATFORM=1 # development or test. Unset means production, the safe way round. NODE_ENV=development # --------------------------------------------------------------------------------------------- # Observability (spec §9.5). Errors need a DSN; logs and traces need the platform's OTLP endpoint. # Without them the app runs, sends nothing, and says so in one log line at start-up. # --------------------------------------------------------------------------------------------- # The project DSN errors are sent to. Public by design (it identifies a project, it grants no read). CONUSAI_DSN= # Set together by the platform when the environment has OTLP instrumentation. The exporter authenticates # with CONUSAI_API_TOKEN, so the headers variable is rarely needed. OTEL_EXPORTER_OTLP_ENDPOINT= OTEL_EXPORTER_OTLP_PROTOCOL= OTEL_EXPORTER_OTLP_HEADERS= # The service name in logs and traces. Default: app-web or app-worker. OTEL_SERVICE_NAME= # The release when CONUSAI_RELEASE is unset (the platform derives it from the commit). OTEL_SERVICE_VERSION= # debug, info, warn or error. Default info. LOG_LEVEL= # --------------------------------------------------------------------------------------------- # Postgres: the three logins of a linked managed database (data-services DS21). web and worker never # see the owner's URL; only the migrate job does. dev/postgres-roles.sql creates them locally. # --------------------------------------------------------------------------------------------- # The runtime role, bound by row-level security. web, worker and migrate. POSTGRES_URL=postgresql://appdev_app:appdev_app@postgres:5432/appdev # The global reader: the three reads that belong to no organization. web, worker and migrate. POSTGRES_READER_URL=postgresql://appdev_reader:appdev_reader@postgres:5432/appdev # The owner. The migrate job alone. POSTGRES_MIGRATE_URL=postgresql://appdev_owner:appdev_owner@postgres:5432/appdev # --------------------------------------------------------------------------------------------- # Sign-in (spec §7). Set by you; MIB Identity does not inject them yet. # --------------------------------------------------------------------------------------------- # At least 32 bytes of random secret; the key the session cookie is sealed with. Generate one with # node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))" AUTH_SESSION_SECRET=replace-me-with-32-bytes-of-random-secret # The one sign-in authority: oidc (default; the OIDC_* variables below) or convex-better-auth (the # app-embedded Better Auth adapter: the BETTER_AUTH_* variables below; `kit template:prune --auth oidc` # removes it from an app that does not use it). Better Auth mode refuses every OIDC_* variable and # AUTH_DEV_LOGIN (blank both), and needs AUTH_IDENTITY_NAMESPACE instead. AUTH_PROVIDER= # Better Auth only: the stable namespace its users are mapped under, e.g. better-auth:notes-production. # Never an issuer URL. Leave it empty for oidc, whose namespace is OIDC_ISSUER. AUTH_IDENTITY_NAMESPACE= # kit:convex-better-auth:begin # Better Auth only: this app's Better Auth Convex deployment (better-auth/, deployed by the migrate job, # or the convex-deploy job on the Convex profile). On ConusAI Cloud the project's `better-auth` Convex # link injects these three (runtime-only); set them here for local development. Its URL, where the web server validates every # request's session and the revocation relay reads the outbox, and its site URL, where Better Auth's # routes are (the app's /api/auth forwards to them). BETTER_AUTH_CONVEX_URL= BETTER_AUTH_CONVEX_SITE_URL= # Better Auth only: the deployment's admin key. Server-side only (web, the worker and the deploy job; # on the Convex profile the deploy job also installs it, with the URL and the namespace, in the app's # own Convex deployment, whose account actions end a deleted person's Better Auth account): it never # validates a person, and never reaches a browser. BETTER_AUTH_CONVEX_ADMIN_KEY= # Better Auth only: Better Auth's own secret, at least 32 random characters. The deploy job alone # holds it and installs it in the Better Auth deployment; the web server never sees it. BETTER_AUTH_SECRET= # Better Auth only: how often, in milliseconds, the web server delivers the revocation outbox (a # sign-out elsewhere, a revoked device, a disabled or deleted account). Default 2000. BETTER_AUTH_RELAY_INTERVAL_MS= # kit:convex-better-auth:end # An OIDC issuer (MIB Identity, or any other) and this app's client. All three, or none: with # none, pages render signed-out and sign-in refuses until a provider is configured. OIDC_ISSUER= OIDC_WEB_CLIENT_ID= # The audience this app's API accepts in an access token. OIDC_AUDIENCE= # Optional RFC 8707 resource URI from the selected identity link, forwarded at sign-in/exchange/refresh. OIDC_RESOURCE= # Only for a confidential client; the default public client uses PKCE and needs none. OIDC_WEB_CLIENT_SECRET= # Optional server-only resource-status credential, limited to this link's audience. Set both or neither. OIDC_INTROSPECTION_CLIENT_ID= OIDC_INTROSPECTION_CLIENT_SECRET= # Optional server-only policy-bridge URL a managed provider (Better Auth) supplies with that credential. # Without it, status uses the issuer's advertised introspection endpoint. Requires the credential. OIDC_RESOURCE_STATUS_URL= # Optional introspection claim an active answer must carry (managed Zitadel: the project-roles claim). # Requires the credential. OIDC_INTROSPECTION_REQUIRED_CLAIM= # Scopes asked of the provider. Default: openid profile email offline_access. OIDC_SCOPES= # Monotonic active-link generation. Existing links use 1; every provider cutover/rollback uses a new value. AUTH_PROVIDER_GENERATION= # Plain-HTTP discovery, outside production only (a local provider). OIDC_ALLOW_INSECURE= # 1 lets anyone in by e-mail address, with no identity provider. Refused at boot in production. AUTH_DEV_LOGIN=1 # 1 drops the Secure flag from the session cookie, for http://127.0.0.1. Refused at boot in production. AUTH_INSECURE_COOKIES=1 # The session cookie's name. Default conusai_session. AUTH_COOKIE_NAME= # Session lifetime in seconds: idle 12 h and absolute 14 days by default. AUTH_SESSION_IDLE_SECONDS= AUTH_SESSION_ABSOLUTE_SECONDS= # --------------------------------------------------------------------------------------------- # This app # --------------------------------------------------------------------------------------------- # The model the AI gateway is asked for when a document is read (modules/documents). AI_MODEL= # Which search backend the knowledge module uses: "vectors" (the default: chunks and queries are # embedded through the AI gateway and stored in the vector store below) or "platform" (the managed # documents-index service). KNOWLEDGE_BACKEND= # Where the vectors backend stores its vectors. Unset: a linked Qdrant service (QDRANT_URL; enabling # document AI links a managed one), and with none linked the knowledge module reports itself # unavailable and the migrate job creates no vector table. "pgvector": the app's own Postgres, chosen # explicitly; it needs a Postgres that has the extension (the platform's default managed Postgres # image does not: create the service from the pgvector image). "qdrant": Qdrant only. KNOWLEDGE_VECTOR_STORE= # The embeddings model the knowledge module asks the gateway for when the backend is vectors. # Required for indexing and search on that backend; the platform injects no default. AI_EMBEDDING_MODEL= # The vector size the vector store is built with; must match the embeddings model's output # (1536 by default, the size of the usual embedding models). VECTOR_DIMENSIONS= # The kinds of page the documents extraction never asks the model about: a comma list of text, table, # drawing, scan, empty. Without it, drawings and empty pages are left out and reported on the document. # An empty value reads every page. DOCUMENTS_EXCLUDED_PAGE_CLASSES= # Where the worker writes the heartbeat its healthcheck reads. Default /tmp/worker-heartbeat. WORKER_HEARTBEAT_FILE= # kit:guides:begin # The guides module's publish credential: a short-lived `guides:publish-code` token the platform # hands the migrate service alone, for a deployment a member started, and revokes when the deploy # job ends. Never set it yourself; without it the migrate job skips publishing code guides and says so. CONUSAI_GUIDES_PUBLISH_TOKEN= # The deployment's provenance JSON sent with it (deployment_id, source_revision, generation, # requested_by), set by the platform beside the token. CONUSAI_GUIDES_DEPLOYMENT= # kit:guides:end # kit:support:begin # The support module's channel: the public key (spk_...) of a Support channel in this app's project, # created in the console under Support -> Channels. It grants nothing by itself; widget sessions are # minted with the deployment token (`support:widget-sessions`). Empty: no launcher. CONUSAI_SUPPORT_CHANNEL= # kit:support:end # --------------------------------------------------------------------------------------------- # `pnpm dev` and `pnpm e2e` (scripts/dev.mjs, e2e/support/stack.ts) # --------------------------------------------------------------------------------------------- # The ports they use on this machine. Defaults: web 3000, fake platform 4010, Postgres 55433; the # end-to-end run uses 3100 and 55432 so it can run beside a development stack. DEV_WEB_PORT= DEV_PLATFORM_PORT= DEV_POSTGRES_PORT= E2E_WEB_PORT= E2E_POSTGRES_PORT=