Configuration
Simmetric Chat has two configuration levels: environment variables validated with Zod at process startup, and a runtime config layer stored in the database that admins can edit from the UI without restarting any service.
Simmetric Chat has two configuration levels: environment variables validated with Zod at process startup, and a runtime config layer stored in the database that admins can edit from the UI without restarting any service.
- Environment variables (process startup) - validated with Zod in
packages/<pkg>/src/config/env.ts;process.exit(1)when invalid. - Runtime config (DB `SystemConfig`) - editable from the admin UI without a restart.
Resolution:
ALWAYS_READONLYkeys (JWT_SECRET,DATABASE_URL,SERVER_PORT,COLLECTOR_PORT,SERVER_URL,COLLECTOR_URL):ENV > Default, not editable from the UI.- All the others:
DB > ENV > Default, editable viaPUT /api/system/settings.
The three packages read .env via path.resolve(__dirname, "../../.env") (independent of the cwd). The reference document is .env.example at the repo root.
Environment variables - server
Schema: packages/server/src/config/env.ts. Defaults shown; REQ = required.
Core / infra
| Variable | Default | Req. | Description |
|---|---|---|---|
NODE_ENV | development | opt | development/production/test |
SERVER_PORT | 3000 | opt | Server API port |
COLLECTOR_PORT | 3210 | opt | Collector port (for the default URL) |
SERVER_URL | http://localhost:3000 | opt | Public server URL |
COLLECTOR_URL | http://localhost:3210 | opt | Collector URL |
WIDGET_SERVICE_URL | http://localhost:3211 | opt | Widget service URL (push cache-bust) |
WIDGET_API_KEY | - | opt | Widget push shared secret; when absent, cache-bust is a no-op (5 min TTL) |
DATABASE_URL | postgresql://simmetricchat:[email protected]:5432/simmetricchat | opt | PostgreSQL URL |
JWT_SECRET | - | REQ | JWT secret |
ENCRYPTION_KEY | - | opt | Base64 of 32 bytes for AES-256-GCM (data-at-rest). Unset → fallback scryptSync(JWT_SECRET, salt) |
SESSION_EXPIRY | 86400000 (24h ms) | opt | JWT expiry |
ALLOWED_ORIGINS | http://localhost:5173,127.0.0.1:5173,localhost:3000 | opt | Global CORS allowlist (CSV). /api/internal/widget excluded |
LOG_LEVEL | info | opt | debug/info/warn/error |
ALLOW_REGISTRATION | false | opt | Self-registration toggle |
DISABLE_TELEMETRY | true | opt | Disables telemetry |
Bootstrap admin (seed)
| Variable | Default | Description |
|---|---|---|
SEED_BOOTSTRAP_ADMIN | true | Auto-seed the admin on first startup (custom transform: false/0/no/off/"" = disabled) |
SEED_ADMIN_USERNAME | admin | Bootstrap admin username |
SEED_ADMIN_PASSWORD | admin123 | Password (min 8); account carries mustChangePassword=true |
SEED_ADMIN_EMAIL | [email protected] | Bootstrap admin email |
LLM
| Variable | Default | Description |
|---|---|---|
LLM_PROVIDER | ollama | openai/anthropic/ollama/openrouter |
LLM_MODEL | gemma4:latest | Default model |
LLM_TEMPERATURE | 0.7 | 0–2 |
LLM_MAX_TOKENS | 4096 | Completion token cap |
LLM_TIMEOUT | 0 | Axios timeout ms (0 = none, for slow local LLMs) |
LLM_API_KEY / LLM_API_BASE_URL | - | Generic overrides |
OPENAI_API_KEY / OPENAI_MODEL | - | OpenAI |
ANTHROPIC_API_KEY / ANTHROPIC_MODEL | - | Anthropic |
OLLAMA_BASE_URL | http://ollama:11434 | Ollama endpoint |
OLLAMA_MODEL / OLLAMA_API_KEY | - | Ollama override |
OPENROUTER_API_KEY / OPENROUTER_BASE_URL / OPENROUTER_MODEL | https://openrouter.ai/api / - | OpenRouter |
Agent watchdog (loop/stall prevention)
| Variable | Default | Description |
|---|---|---|
AGENT_WALLCLOCK_TIMEOUT_MS | 600000 (10 min) | Absolute upper bound for a ReAct request |
AGENT_MAX_TOTAL_TOKENS | 200000 | Prompt+completion total per request |
AGENT_MAX_CONTEXT_BYTES | 500000 (500 KB) | Context array size cap |
AGENT_MAX_TOOL_OUTPUT_LENGTH | 5000 | Per-skill output truncation |
AGENT_MAX_SKILL_EXECUTION_MS | 60000 | Per-skill timeout |
AGENT_LOOP_DETECTION_WINDOW | 3 | Abort after N identical consecutive tool+input |
CHAT_MAX_CONCURRENT_PER_USER | 5 | Per-user chat concurrency limit |
Embedding / Vector DB / OCR / Synthesis
| Variable | Default | Description |
|---|---|---|
EMBEDDING_PROVIDER | local | local/openai/ollama |
EMBEDDING_MODEL | Xenova/all-MiniLM-L6-v2 | Embedding model |
EMBEDDING_API_KEY | - | Embedding API key |
VECTOR_DB_PROVIDER | lancedb | lancedb/qdrant |
VECTOR_DB_URL / VECTOR_DB_API_KEY | - | For remote Qdrant |
OCR_MODEL | glm-ocr:latest | Vision OCR model (model:version format) |
OCR_TIMEOUT | 600000 | Vision OCR timeout |
SYNTHESIS_LLM_MODEL | gemma4:latest | Synthesis pipeline model |
PUPPETEER_EXECUTABLE_PATH | - | Chromium for archive PDF export |
WIKI_EMBED_TIMEOUT_MS | 1800000 (30 min) | Server→collector POST /api/ingest/wiki-pages wait cap for the hourly wiki-consistency reindex; raise for very large pages on slow hosts; 0 disables the cap (ENV-only) |
License, push, email, collector
| Variable | Default | Description |
|---|---|---|
LICENSE_KEY | - | Enterprise license JWT RS256 (vendor-issued; public key embedded in the server source). Absent → Community; the no-.env alternative = the admin-UI paste / PUT /api/license/instance (F-LIC-1) |
COLLECTOR_SECRET | - | REQ - server↔collector shared secret |
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT | - | Web Push; when unset, ephemeral keys are generated in dev |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM | - | SMTP for password reset + backup failure notifications |
LEGACY_PREVIOUS_ENCRYPTION_KEYS | - | CSV base64 32-byte keys for key rotation |
Variables in.env.examplebut managed as DB settings (not inenvSchema):OCR_ENABLED,OCR_PRECHECK_CHARS,STORAGE_PATH(collector),RESET_PASSWORD_STRATEGY.
Environment variables - collector
Schema: packages/collector/src/config/env.ts.
| Variable | Default | Req. | Description |
|---|---|---|---|
COLLECTOR_PORT | 3210 | opt | HTTP port |
COLLECTOR_URL | http://localhost:3210 | opt | Own URL |
SERVER_URL | http://localhost:3000 | opt | Server URL (callback) |
EMBEDDING_PROVIDER | local | opt | local/openai/ollama |
EMBEDDING_MODEL | - | opt | Embedding model |
EMBEDDING_API_KEY | - | opt | API key |
VECTOR_DB_PROVIDER | lancedb | opt | lancedb/qdrant |
VECTOR_DB_URL / VECTOR_DB_API_KEY | - | opt | Qdrant |
OLLAMA_BASE_URL | http://ollama:11434 | opt | Ollama |
STORAGE_PATH | ./storage | opt | Filesystem storage |
COLLECTOR_SECRET | - | REQ | Must match the server |
Environment variables - widget
Schema: packages/widget/src/config/env.ts.
| Variable | Default | Req. | Description |
|---|---|---|---|
NODE_ENV | development | opt | Environment |
WIDGET_PORT | 3211 | opt | HTTP port |
SERVER_URL | http://localhost:3000 | opt | Server URL |
WIDGET_API_KEY | - | REQ | Widget↔server shared secret (must match) |
LOG_LEVEL | info | opt | Log level |
Frontend: noVITE_*variables and no.envfile. The frontend reads branding/config
from the server (GET /api/system/settings,GET /api/license/info).
Feature flags & license
Defined in packages/shared/src/constants/license.ts (FEATURE_FLAGS + defaults). Verified in packages/server/src/services/licenseService.ts.
How to move from Community to Enterprise
- Set
LICENSE_KEY(a vendor-issued RS256 JWT; the public key is embedded in the server source -packages/server/src/services/license-public-key.ts) - or, without touching.envand without a restart, paste it in Settings → Security → License orPUT /api/license/instance(F-LIC-1). - The payload is validated against
licensePayloadSchema(tier,iss,sub,iat,exp,features); probe-first verification: a rejected key is never stored. tier=enterprise→ starts fromENTERPRISE_FEATURE_DEFAULTS+ the JWTfeaturesoverrides.- Source precedence (identical at boot and runtime): the
INSTANCE_LICENSEslot (AES-256-GCM ciphertext) → verified plugin row → ENVLICENSE_KEY→ Community. - Expired/invalid → Community fallback with a warning.
Graceful degradation: getLicenseInfo() checks expiry at runtime; if Enterprise expires while in use, it degrades to Community without a restart. requireFeature(flag) → HTTP 402 when disabled; requireFeatureLimit(flag, model) → 402 when count >= limit (Enterprise = Infinity).
Always-on commodity flags: since the EPA-02 removal the 8 commodity flags no longer exist in FEATURE_FLAGS - webhooks, push_notifications, web search, memory, lead_export, widget_analytics, auto_title, synthesis_rate_limit are always on in Community (commodity routes run without gating). priority_support is not a flag: it is a commercial/SLA contract (EPA-08). 14 flags remain, including widget_credits_editing (widget credits editing, Community false / Enterprise true), custom_agents as a numeric limit (Community 3, Enterprise Infinity), max_skills (Community 3, Enterprise Infinity, F-SKILL-1), max_users (Community 5, Enterprise Infinity - enforced at user creation) and connectors_enabled (Community false, F-CONN-1).
Flag table (14)
| Flag | Type | Community | Enterprise | What it gates |
|---|---|---|---|---|
sso_enabled | bool | false | true | Enterprise SSO |
audit_log_immutable | bool | false | true | Audit log immutability |
white_label | bool | false | true | BRANDING_* branding |
max_workspaces | num | 3 | Infinity | Workspace creation |
max_projects | num | 3 | Infinity | Project creation |
custom_agents | num | 3 | Infinity | Custom agents (limit) |
widget_enabled | bool | false | true | Embeddable widget |
max_widgets | num | 1 | Infinity | Widget creation |
backup_enabled | bool | false | true | Backup system |
max_backup_destinations | num | 1 | Infinity | Backup destinations |
max_users | num | 5 | Infinity | Users (per-license seats) |
connectors_enabled | bool | false | true | Chat connectors (F-CONN-1) |
max_skills | num | 3 | Infinity | Custom skills (F-SKILL-1) |
widget_credits_editing | bool | false | true | Widget credits editing |
Runtime config (DB SystemConfig)
Editable via GET/PUT /api/system/settings (bulk) and PUT /api/system/chat-retention (specific, audited). Defaults live in CONFIG_DEFAULTS (packages/shared/src/constants/permissions.ts).
Key groups: LLM (LLM_PROVIDER, LLM_MODEL, LLM_TEMPERATURE, ...), Embedding/Vector, Server ports/URLs, Auth (SESSION_EXPIRY), Branding (BRANDING_APP_NAME, BRANDING_PRIMARY_COLOR, BRANDING_APP_SUBTITLE, BRANDING_APP_ICON_URL - gated by white_label), Feature (DISABLE_TELEMETRY, ALLOW_REGISTRATION, DLP_ENABLED), OCR (OCR_DEFAULT_MODEL, OCR_DEFAULT_MODE, OCR_DEFAULT_CUSTOM_INSTRUCTIONS, OCR_ENABLED, OCR_PRECHECK_CHARS), Synthesis (SYNTHESIS_LLM_PROVIDER_ID, SYNTHESIS_LLM_MODEL), Retention (upload_draft_retention_days, ALLOW_NON_ADMIN_UPLOAD, chat_message_retention_days).
PUT /api/system/settings always returns 200 with { updated: [...], rejected: [...] } (readonly keys are rejected while the others are applied).
Chat message retention (reaper)
Service: packages/server/src/services/chatMessageReaperJob.ts. DB knob: chat_message_retention_days.
- Pass 1 (soft-delete): tombstones messages of ACTIVE chats beyond
retentionDays. No-op when the value isnull/""/<=0. - Pass 2 (hard-purge): deletes tombstoned rows beyond a 7-day grace period (hardcoded). Always runs, regardless of the retention config (anti-PII leak).
- Cadence: 24h
setInterval, first run at 03:00 local time.isRunningmutex. - Only
PUT /api/system/chat-retention(audited,confirmDataLoss: true,retentionDays: integer|null, min 1); the bulk settings endpoint rejects this key.
Backup retention
Per-job (the retentionDays field on BackupJob, default 30). backupRetentionService.ts cleans logs/files beyond cutoffDate. It is not global.
Encryption key rotation
packages/server/src/services/encryptionService.ts (AES-256-GCM, 32-byte key, 16-byte IV, salt simmetric-chat-encryption-salt).
- Current key:
ENCRYPTION_KEY(base64 32 bytes) when set; elsescryptSync(JWT_SECRET, salt, 32). LEGACY_PREVIOUS_ENCRYPTION_KEYS(CSV base64 32-byte) → decrypt key chain[current, ...previous, scryptLegacy?].encrypt()useschain[0]. There is no automatic re-encryption job: the procedure is a manual decrypt-with-old → re-encrypt-with-new.
v0.28.1 Provenance
| Statement | Source |
|---|---|
| LICENSE_KEY is RS256 (no LICENSE_SECRET - the public key is embedded) | docs/CONFIGURATION.md (LICENSE_KEY row, row-time) + docs/ADMIN.md (Enterprise License) - F-LIC-1 |
| ALWAYS_READONLY = 6 infra keys (LICENSE_SECRET no longer readonly - it no longer exists) | docs/CONFIGURATION.md:42 (row-time) |
| Feature flags = 14 (+max_users 5/∞, connectors_enabled false/true, max_skills 3/∞) | packages/shared/src/constants/license.ts (row-time: FEATURE_FLAGS ×14 + COMMUNITY/ENTERPRISE defaults) - F-SKILL-1, F-CONN-1 |
| WIKI_EMBED_TIMEOUT_MS 1800000 (ENV-only) | docs/CONFIGURATION.md:107 (row-time) |
Operational reference - Simmetric Chat v0.28.1 (docs updated 2026-10-02, census SHA `aca5b8ff`).
Cross-link
- Settings endpoints: 04 - API Reference
- Internal features: 05 - Feature Guide
- Deploy env vars: 09 - Deployment