# ===================================================================== # WeKnora environment variable configuration example # --------------------------------------------------------------------- # How to use: # 1. Copy this file to .env: cp .env.example .env # 2. Replace all placeholders with real values (especially items marked ⚠️ required) # 3. Make sure .env is not committed to version control # # How config is injected: # - The app service loads every variable in this file via env_file: .env; # - docreader / mcp / frontend / odl-hybrid have no env_file and only consume # variables listed in their own environment sections (see docker-compose.yml). # # Section order: # A. Deployment basics images, runtime, networking # B. Data & storage database, Redis, file storage, object storage # C. Retrieval & graph vector stores, knowledge graph # D. Models LLM/VLM/Ollama, builtin models # E. Document parsing Docreader, task timeouts # F. Auth & space isolation JWT/AES, registration, RBAC, OIDC # G. Agent & sandbox Sandbox, Skills, Agent timeouts # H. Optional integrations web search, MCP Server # I. Observability Langfuse # J. Security & tuning SSRF, proxies, concurrency # ===================================================================== # ##################################################################### # A. Deployment basics # ##################################################################### # ========== A1. Image version & build ========== # WeKnora image tag: latest (stable) / main (latest development). When upgrading to a pinned release, set this to the git tag (e.g. v0.7.0, including the v prefix) and run docker compose pull. WEKNORA_VERSION=latest # Alpine apk mirror (used when building the app image; leave empty for the default; in China you can set mirrors.tencent.com for speed). APK_MIRROR_ARG=mirrors.tencent.com # Whether to link the anydoc in-process parser when building the app image (default 1). Set 0 to skip the Rust toolchain and shorten build time; the engine then shows as unavailable on the settings page. # WITH_ANYDOC=1 # apt mirror used when building the docreader image (leave empty for the default). # APT_MIRROR= # ========== A2. Runtime basics ========== # gin run mode: debug (development, verbose logs + Swagger) / release (production, Swagger disabled). GIN_MODE=release # Log level: debug / info / warn / error / fatal, default debug. # LOG_LEVEL=debug # Log file path (leave empty to write stdout only); when LLM_DEBUG_LOG is on, llm_debug.log is written in the same directory. # LOG_PATH= # Custom log format template (leave empty to use the built-in default). # LOG_FORMAT= # LLM debug log: write the full request/response of every LLM call to a separate file, for debugging context issues. # Values: true (write llm_debug.log next to LOG_PATH) / false/empty (off) / an explicit file path. # LLM_DEBUG_LOG=true # Timezone, default Asia/Shanghai. Affects system time display and log timestamps. # Common values: Asia/Shanghai, Asia/Tokyo, America/New_York, Europe/London, UTC. TZ=Asia/Shanghai # System default language (BCP-47), used as the fallback for the {{language}} placeholder in prompts. # Priority: Accept-Language request header > this variable > built-in default (en-US). # WEKNORA_LANGUAGE=zh-CN # Frontend UI default language (BCP-47). Options: zh-CN | en-US | ru-RU | ko-KR. # Priority: user-selected language (localStorage) > this variable > built-in default zh-CN. # Only affects first-time visitors / users who have not switched language manually; restart the frontend container after changing, no image rebuild needed. # DEFAULT_LOCALE=en-US # Default language fallback when building the frontend image (only used when Docker runtime config.js is not in use). # VITE_DEFAULT_LOCALE=en-US # Run database migrations on startup; set false to disable (enabled by default). AUTO_MIGRATE=true # Automatically recover dirty migration state (default true). AUTO_RECOVER_DIRTY=true # On startup, promote the user with this email to system admin (idempotent; only PROMOTES, never DEMOTES). # Useful for first-time docker-compose/k8s bootstrap; later promote/revoke via the SystemAdmin page. # WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL= # ========== A3. Networking & external URLs ========== # App service hostname, default app (Docker internal service name); for a remote backend, set the remote address. APP_HOST=app # App service host-mapped port, default 8080 (only affects host access, not container-to-container traffic). APP_PORT=8080 # NGINX upstream port for the backend, default 8080 (port the App container listens on). # Keep the default for local deploys; for remote deploys set the actual remote App port. # APP_BACKEND_PORT=8080 # NGINX upstream scheme for the backend, default http; set https if the remote backend is HTTPS. # APP_SCHEME=http # Frontend service port, default 80. FRONTEND_PORT=80 # Docreader address (used by app to connect to docreader). DOCREADER_ADDR=docreader:50051 # Docreader transport: grpc / http / https. DOCREADER_TRANSPORT=grpc # Public URL of the app (affects image/file links for IM channels). # Set to a URL that IM platforms/clients can reach over the public internet, e.g. https://weknora.example.com. # For IM to display knowledge-base images, the IM side must fetch them via a public http URL. Pick one: # (A) The storage backend itself is publicly reachable (object storage uses a public endpoint, or MINIO_ENDPOINT is a public host); # then resource:// falls back to a backend presigned URL and this variable is not needed. # (B) Set this variable so resource:// images are rewritten to /r/ via WeKnora (nginx must proxy /r/). # Default MinIO deploys (minio:9000 is internal) are easiest with (B); the local backend can only use (B). # Private IPs and localhost are not reachable by Feishu/WeCom/Slack and similar IM platforms; for local dev use ngrok/cloudflared/frp. # APP_EXTERNAL_URL= # Frontend public origin, used for absolute URLs such as invite links (leave empty for host-relative). # FRONTEND_BASE_URL= # Default form of file references in API responses: handle (default) / public. # handle — return an internal resource://; the client must then call the authenticated /files proxy. # public — return a loadable http(s) URL that a third-party app can render without a second request. # A single request can override this with ?resource_urls=handle|public; see README "API reference" for scope. # public depends on APP_EXTERNAL_URL above (or a storage backend that is itself publicly reachable); # if an external URL cannot be generated, the reference stays as resource:// and the client can still fall back to /files. # Note: public issues a time-limited anonymous readable link per referenced file (WeKnora 2 hours / MinIO 24 hours). # Anonymous embed channels and knowledge-base-scoped API keys ignore this variable and always return handle. # Also configure SYSTEM_AES_KEY so grant rows can be reused, direct-link URLs stay stable, and write pressure on read APIs drops. # RESOURCE_URL_MODE=handle # Public MCP Server port (default 8082). # MCP_PORT=8082 # ##################################################################### # B. Data & storage # ##################################################################### # ========== B1. Database ⚠️ required ========== # Primary database type: postgres / mysql / sqlite. DB_DRIVER=postgres # Database host. DB_HOST=postgres # Database port. DB_PORT=5432 # Database username. DB_USER=postgres # Database password. DB_PASSWORD=postgres123!@# # Database name. DB_NAME=WeKnora # Used with the SQLite driver (DB_DRIVER=sqlite); ignored for postgres/mysql. # DB_PATH=./data/weknora.db # ========== B2. Redis / streaming / Asynq queues ========== # --- Stream backend and Redis connection --- # Stream backend: memory / redis. In redis mode the Asynq task queue also uses this Redis. STREAM_MANAGER_TYPE=redis # Redis address (leave empty to disable Redis; Lite mode uses memory). REDIS_ADDR=redis:6379 # Redis username (Redis 6.0+ ACL, optional). # REDIS_USERNAME= # Redis password; leave empty if unset. REDIS_PASSWORD=redis123!@# # Redis database index, default 0. REDIS_DB=0 # Redis key prefix, for namespace isolation. REDIS_PREFIX=stream: # Redis channel namespace suffix (to isolate multiple deployments sharing one Redis). # WEKNORA_REDIS_NAMESPACE= # Asynq client Redis read/write timeout (milliseconds, default 500; writes can go up to 1000). # WEKNORA_REDIS_OP_TIMEOUT_MS=500 # --- Redis TLS (encrypted transport for managed Redis such as AWS ElastiCache; off by default) --- # Enable TLS. # REDIS_USE_TLS=false # Server name used for TLS cert verification and SNI (useful when the address is an IP). # REDIS_TLS_SERVER_NAME= # Skip server certificate verification. Insecure; only for dev/self-signed certs. Disable in production. # REDIS_TLS_INSECURE_SKIP_VERIFY=false # --- Asynq worker concurrency --- # Each pool is a separate asynq.Server with hard isolation; leave empty or <1 to use code defaults. Values are "per-instance floor concurrency"; multi-replica deploys add up by replica count. Can also be changed at runtime in system settings (asynq.*_concurrency) without restart. Format: VARIABLE = default — queues consumed / notes # default + chat_attachment (main document-parse path; chat_attachment has higher weight so bulk imports do not starve it) # WEKNORA_ASYNQ_CORE_CONCURRENCY=8 # postprocess (knowledge-base post-processing/fan-out after parse; its own pool so it is not queued behind long DocReader jobs and stays low-latency) # WEKNORA_ASYNQ_POSTPROCESS_CONCURRENCY=2 # summary / multimodal / graph / question (summaries, image multimodal, graph chunk extraction, question generation; LLM/VLM heavy, tune to provider budget) # WEKNORA_ASYNQ_ENRICHMENT_CONCURRENCY=12 # sync + low (datasource sync, FAQ import, KB clone/delete, batch reparse, knowledge move; long-running, does not borrow from shared) # WEKNORA_ASYNQ_MAINTENANCE_CONCURRENCY=4 # Elastic pool that can borrow CORE/ENRICHMENT queues (atomic dequeue is safe); does not consume POSTPROCESS/MAINTENANCE # WEKNORA_ASYNQ_SHARED_CONCURRENCY=6 # Dedicated wiki-generation pool (LLM-bound; tune to provider concurrency budget) # WEKNORA_WIKI_ASYNQ_CONCURRENCY=8 # Per-model concurrency cap for background tasks (0/negative disables; interactive chat is not limited by this) # WEKNORA_MODEL_MAX_CONCURRENCY=32 # ========== B3. File storage (general) ========== # File storage type: local / minio / cos / tos / s3 / obs / oss / dummy. STORAGE_TYPE=local # Allow-list of storage types users may use (comma-separated; leave empty to allow all). # STORAGE_ALLOW_LIST=local,minio,cos,tos,s3,obs,oss # Local storage root directory (used when STORAGE_TYPE=local). LOCAL_STORAGE_BASE_DIR=/data/files # Local storage path prefix (for legacy backfill; usually leave empty). # LOCAL_STORAGE_PATH_PREFIX= # Unified file size limit (MB, default 50). Affects knowledge-base/attachment uploads, docreader gRPC messages, and browser knowledge-base validation. This is deploy-time config: Go/Nginx/docreader/browser each read it once at startup; changing it at runtime has no effect — restart all four layers together. # MAX_FILE_SIZE_MB=50 # 技能 zip 上传与 GitHub/ClawHub 来源下载上限(MB,默认 256)。与知识库上传分开:ppt-master 一类技能包会超过 50MB。GitHub zipball 按整仓压缩包计,不是 SKILL.md 子树;仓过大仍会在下载阶段被拒。未设置时不低于 MAX_FILE_SIZE_MB,且不超过 512。Nginx 只对 POST /api/v1/skills/catalog 与 POST /api/v1/sandbox-configs/{id}/skills 放宽到这个值,/install、PATCH 等子路径仍是 MAX_FILE_SIZE_MB。改后须重启 app 与 frontend。 # MAX_SKILL_BUNDLE_SIZE_MB=256 # ========== B4. Object storage providers (pick one based on STORAGE_TYPE) ========== # ----- MinIO (STORAGE_TYPE=minio) ----- # MinIO endpoint (host:port). For IM channels this must be a host the IM platform can reach on the public internet (do not use minio:9000). # MINIO_ENDPOINT=minio:9000 # MINIO_ACCESS_KEY_ID=your_minio_access_key # MINIO_SECRET_ACCESS_KEY=your_minio_secret_key # MINIO_BUCKET_NAME=your_minio_bucket_name # MINIO_PATH_PREFIX= # MINIO_USE_SSL=false # MinIO ports (used by compose mappings). # MINIO_PORT=9000 # MINIO_CONSOLE_PORT=9001 # ----- Tencent Cloud COS (STORAGE_TYPE=cos) ----- # COS_SECRET_ID=your_cos_secret_id # COS_SECRET_KEY=your_cos_secret_key # COS_REGION=ap-guangzhou # COS_BUCKET_NAME=your_cos_bucket_name # COS_APP_ID=your_cos_app_id # COS_PATH_PREFIX=your_cos_path_prefix # COS_TEMP_BUCKET_NAME=your_cos_temp_bucket_name # COS_TEMP_REGION=your_cos_temp_region # ----- Volcengine TOS (STORAGE_TYPE=tos) ----- # TOS_ENDPOINT=https://tos-cn-beijing.volces.com # TOS_REGION=cn-beijing # TOS_ACCESS_KEY=your_tos_access_key # TOS_SECRET_KEY=your_tos_secret_key # TOS_BUCKET_NAME=your_tos_bucket_name # TOS_PATH_PREFIX=your_tos_path_prefix # TOS_TEMP_BUCKET_NAME=your_tos_temp_bucket_name # TOS_TEMP_REGION=your_tos_temp_region # If you use AWS S3 as file storage, configure the following # AWS S3 access endpoint (optional; leave empty to use the AWS standard endpoint for the Region, e.g. https://s3.amazonaws.com) # ----- AWS S3 (STORAGE_TYPE=s3) ----- # S3_ENDPOINT=https://s3.amazonaws.com # S3_REGION=us-east-1 # AWS S3 Access Key (optional; if both AK/SK are empty, the AWS SDK default credential chain is used, # supporting EC2/ECS/EKS IAM Role, IRSA/Web Identity, environment variables, and shared config files) # AWS S3 Secret Key (must be set together with Access Key, or both left empty) # S3_SECRET_KEY=your_s3_secret_key # S3_ACCESS_KEY=your_s3_access_key # S3_BUCKET_NAME=your_s3_bucket_name # S3_PATH_PREFIX=weknora/ # S3_USE_SSL=true # S3_FORCE_PATH_STYLE=false # ----- Huawei Cloud OBS (STORAGE_TYPE=obs) ----- # OBS_ENDPOINT=obs.cn-north-4.myhuaweicloud.com # OBS_REGION=cn-north-4 # OBS_ACCESS_KEY=your_obs_access_key # OBS_SECRET_KEY=your_obs_secret_key # OBS_BUCKET_NAME=your_obs_bucket_name # OBS_PATH_PREFIX=weknora/ # OBS_USE_SSL=true # OBS_PROXY_DOMAIN=https://your-domain.com/obs # ----- Alibaba Cloud OSS (STORAGE_TYPE=oss) ----- # OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com # OSS_REGION=cn-hangzhou # OSS_ACCESS_KEY=your_oss_access_key # OSS_SECRET_KEY=your_oss_secret_key # OSS_BUCKET_NAME=your_oss_bucket_name # OSS_PATH_PREFIX=weknora/ # OSS_TEMP_BUCKET_NAME=your_oss_temp_bucket_name # OSS_TEMP_REGION=your_oss_temp_region # ##################################################################### # C. Retrieval & graph # ##################################################################### # ========== C1. Vector store / retrieval engine ========== # Vector storage type (comma-separated for multiple drivers): postgres / elasticsearch_v7 / elasticsearch_v8 / # opensearch / qdrant / milvus / weaviate / doris / tencent_vectordb. RETRIEVE_DRIVER=postgres # Timeout for parallel retrieval across multiple vector stores (seconds; takes effect when RETRIEVE_DRIVER lists multiple drivers). # MULTI_STORE_RETRIEVE_TIMEOUT_SEC= # ----- Elasticsearch v7/v8 ----- # ELASTICSEARCH_ADDR=http://localhost:9200 # ELASTICSEARCH_USERNAME=your_elasticsearch_username # ELASTICSEARCH_PASSWORD=your_elasticsearch_password # ELASTICSEARCH_INDEX=WeKnora # ----- OpenSearch (single-node profile in the dev compose) ----- # OPENSEARCH_ADDR=http://opensearch:9200 # OPENSEARCH_USERNAME= # OPENSEARCH_PASSWORD= # OPENSEARCH_INSECURE_SKIP_VERIFY=false # OPENSEARCH_INDEX=WeKnora # ----- Qdrant ----- # QDRANT_HOST=qdrant # QDRANT_PORT=6334 # QDRANT_REST_PORT=6333 # QDRANT_COLLECTION=weknora_embeddings # QDRANT_API_KEY=your_qdrant_api_key # QDRANT_USE_TLS=false # ----- Milvus ----- # MILVUS_ADDRESS=milvus:19530 # MILVUS_COLLECTION=weknora_embeddings # MILVUS_METRIC_TYPE=IP # IP (default) / COSINE / L2; changing this requires recreating the collection. # MILVUS_USERNAME=your_milvus_username # MILVUS_PASSWORD=your_milvus_password # MILVUS_DB_NAME=your_milvus_db_name # ----- Weaviate ----- # From inside a container use service:port (do not use localhost / host-mapped ports). # WEAVIATE_HOST=weaviate:8080 # WEAVIATE_GRPC_ADDRESS=weaviate:50051 # WEAVIATE_SCHEME=http # WEAVIATE_AUTH_ENABLED=false # Set true and configure WEAVIATE_API_KEY when enabling APIKey/OIDC. # WEAVIATE_API_KEY=your_secret_key # WEAVIATE_COLLECTION=your_weaviate_db_name # ----- Apache Doris 4.1+ ----- # DORIS_ADDR=doris-fe:9030 # FE MySQL-protocol address. # DORIS_HTTP_PORT=8030 # FE HTTP, Stream Load partial-update entrypoint. # DORIS_DATABASE=weknora # DORIS_USERNAME=root # DORIS_PASSWORD= # DORIS_TABLE_PREFIX=weknora_embeddings # DORIS_COMPAT_MODE=auto # auto (detect) / legacy (cosine_distance) / inner_product_duplicate; not interchangeable after tables are created. # compose ports. # DORIS_FE_HTTP_PORT=8030 # DORIS_FE_MYSQL_PORT=9030 # DORIS_BE_HTTP_PORT=8040 # ----- Tencent Cloud Tencent VectorDB ----- # TENCENT_VECTORDB_ADDR=http://your-instance.tencentvectordb.com # TENCENT_VECTORDB_USERNAME=root # TENCENT_VECTORDB_API_KEY=your_tencent_vectordb_api_key # TENCENT_VECTORDB_DATABASE=weknora # TENCENT_VECTORDB_COLLECTION=weknora_embeddings # TENCENT_VECTORDB_REPLICA_NUMBER=1 # ========== C2. Knowledge graph (Neo4j, optional) ========== # Sole global switch for the knowledge graph. Anything other than true disables graph build and retrieval (build calls an LLM and can take a long time). # Note: ENABLE_GRAPH_RAG was replaced by NEO4J_ENABLE as of v0.1.6; the Go app no longer reads it and it has been removed. # NEO4J_ENABLE=false # Neo4j connection URI. bolt:// connects directly to a single instance (recommended, no routing overhead); neo4j:// uses cluster routing discovery (works for single-node too, but adds a probe). # WeKnora deploys a single Neo4j by default, so use bolt://. # NEO4J_URI=bolt://neo4j:7687 # NEO4J_USERNAME=neo4j # NEO4J_PASSWORD=password # ##################################################################### # D. Models # ##################################################################### # ========== D1. LLM / VLM / Ollama ========== # If Ollama is unavailable, only warn and do not block startup (takes effect when true; default is not to block, so missing Ollama does not fail startup). OLLAMA_OPTIONAL=true # Ollama base URL (connect to local or remote Ollama). OLLAMA_BASE_URL=http://host.docker.internal:11434 # Batch embedding size (leave empty to use the code default). # BATCH_EMBED_SIZE= # Timeout for a single VLM HTTP request (seconds, default 180; increase if slow endpoints hit context deadline). # VLM_HTTP_TIMEOUT_SECONDS=180 # Dump raw LLM streams (for debugging context issues; 1 enables, default directory ~/.weknora/investigate/llm-stream). # WEKNORA_LLM_STREAM_RAW_DUMP= # WEKNORA_LLM_STREAM_RAW_DUMP_DIR= # ========== D2. Builtin model declarative config (optional) ========== # After enabling config/builtin_models.yaml (see config/builtin_models.yaml.example), ${NAME} placeholders # in the YAML are filled from these variables at app startup. Names are defined by the YAML; common names are shown below. # See docs/BUILTIN_MODELS.md. # # Builtin chat model # LLM_MODEL_NAME= # LLM_BASE_URL= # LLM_API_KEY= # LLM_PROVIDER=openai # # Builtin embedding model # EMBEDDING_MODEL_NAME= # EMBEDDING_BASE_URL= # EMBEDDING_API_KEY= # EMBEDDING_PROVIDER=openai # # Builtin rerank model # RERANK_MODEL_NAME= # RERANK_BASE_URL= # RERANK_API_KEY= # RERANK_PROVIDER=generic # ##################################################################### # E. Document parsing # ##################################################################### # ========== E1. Docreader gRPC and general parsing ========== # ----- gRPC server TLS / auth (shared by docreader and app; client and server must use the same values) ----- # Enable gRPC TLS. Default false (docreader is only called by app on the compose internal network, so plaintext is fine). # GRPC_TLS_ENABLED=false # Server cert / client cert (fill for mTLS). Required on the server when TLS is enabled. # GRPC_TLS_CERT=/path/to/server.crt # Private key for the certificate. # GRPC_TLS_KEY=/path/to/server.key # CA cert: client uses it to verify the server cert; on the server with mTLS it is the CA that issued the client cert. # GRPC_TLS_CA=/path/to/ca.crt # TLS SNI / certificate verification name. Set this to the hostname in the cert when DOCREADER_ADDR is an IP, or when it does not match the cert CN. # GRPC_TLS_SERVER_NAME= # Server side: when true, require the client to present a certificate (mutual TLS / mTLS). # GRPC_MTLS_REQUIRE_CLIENT_CERT=false # gRPC per-RPC auth token (must match on client and server). If enabled, strongly prefer TLS as well, # otherwise the token is sent in plaintext with every RPC; when GRPC_TLS_ENABLED=true the client refuses to send the token on an insecure connection. # GRPC_AUTH_TOKEN=your-secret-token-at-least-16-bytes # ----- gRPC service parameters ----- # Max concurrent gRPC server workers (default 4). # DOCREADER_GRPC_MAX_WORKERS=4 # Max gRPC message size (MB, default 50). Usually follows MAX_FILE_SIZE_MB; oversized scanned-page images may exceed this. # DOCREADER_GRPC_MAX_FILE_SIZE_MB=50 # gRPC listen port (default 50051). Usually leave as-is; must match the docreader port in docker-compose. # DOCREADER_GRPC_PORT=50051 # ----- General parsing ----- # Max pages when parsing DOCX (default 0 = unlimited). A positive integer (e.g. 500) caps parse cost for huge Word files. # DOCREADER_DOCX_MAX_PAGES=0 # MarkItDown (heavy parser) concurrent workers. 0 disables rate limiting; default 1 suits a single machine / limited GPU. # DOCREADER_MARKITDOWN_MAX_WORKERS=1 # Outbound HTTP/HTTPS proxy (used when fetching web pages / remote images). # DOCREADER_EXTERNAL_HTTP_PROXY= # DOCREADER_EXTERNAL_HTTPS_PROXY= # ========== E2. OpenDataLoader hybrid (optional; knowledge-base parser_engine_rules with engine: opendataloader) ========== # ODL heavy-parser concurrency (default 1). # DOCREADER_ODL_MAX_WORKERS=1 # Hybrid mode switch: off (default, local fast parser only) / docling-fast etc. (requires a separate odl-hybrid container). # DOCREADER_ODL_HYBRID=off # odl-hybrid service URL. Code default is http://127.0.0.1:5002; docker compose injects http://odl-hybrid:5002. # DOCREADER_ODL_HYBRID_URL=http://odl-hybrid:5002 # Hybrid mode selection (auto); usually leave unchanged. # DOCREADER_ODL_HYBRID_MODE=auto # Whether to fall back to fast mode if hybrid fails (default false). # DOCREADER_ODL_HYBRID_FALLBACK=false # Whether hybrid markdown output keeps HTML (default false). # DOCREADER_ODL_MARKDOWN_WITH_HTML=false # ----- odl-hybrid container parameters (only with --profile odl-hybrid) ----- # Extra args passed to odl-hybrid. Default --no-ocr (digital PDFs); use --force-ocr for scans (needs EasyOCR+libGL). # ODL_HYBRID_EXTRA_ARGS=--no-ocr # odl-hybrid public port (used by the dev compose mapping). # ODL_HYBRID_PORT=5002 # Max seconds the app waits for odl-hybrid to become ready at startup. # ODL_HYBRID_STARTUP_WAIT_SEC=180 # ========== E3. PDF rendering ========== # PDF render worker count (cross-document concurrency, default 1). # DOCREADER_PDF_RENDER_MAX_WORKERS=1 # Parallel processes for "scanned-page render" inside a single PDF (pdfium is not thread-safe, so parallelism is by process). Default is min(4, cpu); serial render of large scans on CPU-limited containers can take hours. Raising this helps a lot (measured 874 pages: 117s -> 30s with 4 processes -> 17s with 8). The container needs enough CPU to benefit. # DOCREADER_PDF_RENDER_PARALLELISM=4 # Render DPI (default 200). # DOCREADER_PDF_RENDER_DPI=200 # Rendered JPEG quality (default 85). # DOCREADER_PDF_JPEG_QUALITY=85 # Max long-edge pixels of a rendered page image (default 2000). Prevents huge pages from producing 100+MP images that blow the gRPC message limit; lowering it further shrinks images, but too small hurts OCR (dense Chinese: recommend >=1600). # DOCREADER_PDF_RENDER_MAX_EDGE=2000 # ========== E4. PDF scan detection and force-scan ========== # Force every PDF page to be parsed as a scan (render to image, then OCR/VLM). Useful when the text layer is poor but pages are OCR-able; speed and model cost both go up. Default false (auto-detect via the thresholds below). # DOCREADER_PDF_FORCE_SCANNED=false # Auto-detect: a page is treated as scanned if image-object coverage >= this ratio (default 0.5). # DOCREADER_PDF_SCAN_IMAGE_RATIO=0.5 # Auto-detect: a page with fewer than this many text characters is treated as "no usable text layer" (default 10). # DOCREADER_PDF_SCAN_MIN_CHARS=10 # ========== E5. PDF layout / text extraction (native text pages) ========== # Layout-aware text extraction: rebuild a readable reading order (default true). # DOCREADER_PDF_LAYOUT_ORDERING=true # When glyphs have no explicit spaces (common in OCR/search text), insert a space if the gap exceeds this multiple of median character width (default 0.4). # DOCREADER_PDF_WORD_GAP_WIDTH_RATIO=0.4 # Page-width ratio threshold for dropping narrow sidebars (arXiv sidebar watermarks, page-number labels) (default 0.12). # DOCREADER_PDF_MARGIN_COL_WIDTH_RATIO=0.12 # Minimum characters on a line before the font-size heuristic may promote it to a markdown heading (default 8). # DOCREADER_PDF_MIN_HEADING_LINE_CHARS=8 # Promote visually larger lines to markdown headings (uses rect height as a font-size proxy, default true). # DOCREADER_PDF_DETECT_HEADINGS=true # Drop invisible text (render-mode 3), off-page text, and degenerate text to prevent hidden-text injection (default true). # DOCREADER_PDF_FILTER_HIDDEN_TEXT=true # Clean pdfium placeholders (U+FFFE) and soft hyphens; strip vector-chart axis/legend text (default true). # DOCREADER_PDF_SANITIZE_TEXT=true # Strip fragment text in chart regions (used with SANITIZE_TEXT, default true). # DOCREADER_PDF_STRIP_CHART_DEBRIS=true # ========== E6. PDF embedded-image extraction (native text pages) ========== # Extract embedded bitmaps from native pages for VLM/OCR analysis (default true). # DOCREADER_PDF_EXTRACT_EMBEDDED_IMAGES=true # Minimum pixel width and height to keep an embedded image (default 80; smaller ones are dropped). # DOCREADER_PDF_EMBED_MIN_PIXELS=80 # Minimum fraction of page area an embedded image must occupy, otherwise it is dropped (default 0.01). # DOCREADER_PDF_EMBED_MIN_AREA_RATIO=0.01 # If the same image appears on a fraction of text pages >= this value, treat it as a running header/footer and dedupe (default 0.5). # DOCREADER_PDF_EMBED_REPEAT_PAGE_FRAC=0.5 # Hard cap on embedded images extracted per document (default 50). # DOCREADER_PDF_EMBED_MAX_IMAGES=50 # ========== E7. PDF chart-region detection (vector figure areas) ========== # Render detected vector chart regions (no embedded bitmap) to JPEG for VLM/OCR (default true). # DOCREADER_PDF_RENDER_VECTOR_FIGURES=true # Minimum characters per line for a region to be treated as a chart (default 18). # DOCREADER_PDF_MIN_CHART_REGION_CHARS=18 # Minimum area ratio for a region to be treated as a chart (default 0.015). # DOCREADER_PDF_MIN_CHART_REGION_AREA=0.015 # Maximum area ratio for a region to be treated as a chart (above this it is not a chart, default 0.42). # DOCREADER_PDF_MAX_CHART_REGION_AREA=0.42 # Maximum figure height as a fraction of page height; above this it is not treated as a standalone chart (default 0.38). # DOCREADER_PDF_MAX_FIGURE_HEIGHT_RATIO=0.38 # ========== E8. Document processing / task timeouts ========== # Total timeout for a single document-processing job (Go duration, default 2h; increase for large files). # WEKNORA_DOCUMENT_PROCESS_TIMEOUT=2h # Timeout for a single DocReader RPC (default 30m; must be less than the item above). # WEKNORA_DOCREADER_CALL_TIMEOUT=30m # Knowledge-base housekeeping (reclaim dirty data stuck in processing; false disables, not recommended). # WEKNORA_HOUSEKEEPING_ENABLED=true # How long chat-attachment parse artifacts are kept (hours, default 24). # WEKNORA_CHAT_ATTACHMENT_TTL_HOURS=24 # Max time a single Q&A turn waits for attachment parsing to finish (seconds, default 60; increase for large/scanned files). # WEKNORA_CHAT_ATTACHMENT_WAIT_TIMEOUT_SEC=60 # Attachment VLM OCR tuning (scans / image-like documents): concurrency / max pages. # WEKNORA_CHAT_ATTACHMENT_OCR_CONCURRENCY=8 # WEKNORA_CHAT_ATTACHMENT_OCR_MAX_PAGES=8 # ========== E9. Feishu cloud-doc parse mode ========== # Controls the parse path when syncing new-style cloud docs (docx) from Feishu Drive / Feishu Wiki: # export (default, leave empty): use the async export API to download a .docx binary and hand it to docreader. # Images are inlined into the parent document (parent_chunk_id linked to the same knowledge), so retrieval / # wiki / agent can all associate image content. Cost: slower sync; file-block attachments inside Feishu docx # are not downloaded with the export (they are dropped and need a separate sync); spreadsheets / bitable # in the cloud doc become tables embedded in the docx, which WeKnora cannot parse. # blocks: convert via the blocks API to Markdown. Faster, keeps attachments inside the docx; but images become # empty `![image]()` placeholders and are stored as standalone knowledge entries with no content link to the # parent (retrieval / wiki / agent cannot associate images back to the document); spreadsheets become # markdown tables, with risk of losing merged cells and other styling. # Use blocks when you do not need image association, want speed, need to keep attachments, or need spreadsheet styling. # This is a temporary option; if a better parse path appears later, this env var will be removed and replaced. # FEISHU_DOCX_PARSE_MODE=export # ##################################################################### # F. Auth & space isolation # ##################################################################### # ========== F1. Secrets ⚠️ required (change the defaults in production) ========== # JWT signing secret, used for frontend login refresh tokens (if empty, a random one is generated at startup). JWT_SECRET=weknora-jwt-secret # AES-256 master key for encrypting sensitive fields at rest in the database (must be 32 bytes). # Encrypted: tenants.api_key, model API keys, vector-store credentials, web search provider keys, # WeKnoraCloud.AppSecret, etc. If missing or the wrong length, already-encrypted fields cannot be decrypted (UI shows empty; you must re-enter them). # In production you must set this explicitly and keep it safe; if lost, all encrypted data is unrecoverable. # Note: TENANT_AES_KEY / CRYPTO_MASTER_KEY / CRYPTO_SALT are deprecated. As of v0.4.0 CryptoService (PBKDF2) was removed and encryption uses SYSTEM_AES_KEY. The Go app no longer reads those three variables; they have been removed. SYSTEM_AES_KEY=weknora-system-aes-key-32bytes!! # ========== F2. Registration & space policy ========== # Disable new user registration (recommended true in production). # DISABLE_REGISTRATION=false # When enabled, passwords must include upper/lowercase letters, digits, and a special character from: !@#$%^&*()_+-=[]{}|;:,.<>? # WEKNORA_AUTH_COMPLEX_PASSWORD_ENABLED=false # Default space policy after public registration: create_personal (default, auto-create a personal space) / tenantless. # WEKNORA_AUTH_DEFAULT_TENANT_MODE=create_personal # Whether ordinary users can create spaces after login (default true; false means they can only join by invite; super-admins are unaffected). # WEKNORA_TENANT_SELF_SERVICE_CREATION_ENABLED=true # Enable space-level RBAC (true = enforce by default / false = observe-only, log but do not block). # WEKNORA_TENANT_ENABLE_RBAC=true # Enable cross-space access (requires the user's CanAccessAllTenants permission; default false). For a cross-space admin, you must grant that permission in the database by hand. # WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS=false # Max spaces a non-super-admin user can self-create (>0 enforces a cap; =0 uses the default; <0 disables the cap). # WEKNORA_TENANT_MAX_OWNED_PER_USER= # Whether creating a space auto-generates a full_access API Key and returns the token in the response (default false; compatibility fallback for older versions). # WEKNORA_TENANT_AUTO_CREATE_API_KEY=false # Default storage quota for new spaces (GB, default 10). # WEKNORA_TENANT_DEFAULT_STORAGE_QUOTA_GB=10 # Space invite-link TTL (Go duration, default 168h = 7 days). # WEKNORA_INVITATION_TTL=168h # Global switch: when inviting an already-registered user, join them to the space immediately. Default false; can be changed on the system settings page. # WEKNORA_TENANT_AUTO_ACCEPT_INVITATION=false # Audit log retention in days (0 disables cleanup, default 90). # WEKNORA_AUDIT_RETENTION_DAYS=90 # ========== F3. OIDC auth (optional; enable with OIDC_AUTH_ENABLE=true) ========== # OIDC_AUTH_ENABLE=false # OIDC_AUTH_ISSUER_URL=http://127.0.0.1:5556/dex # OIDC_AUTH_DISCOVERY_URL=http://127.0.0.1:5556/dex/.well-known/openid-configuration # OIDC_AUTH_PROVIDER_DISPLAY_NAME=OIDC # OIDC_AUTH_CLIENT_ID=client_id_for_oidc_client # OIDC_AUTH_CLIENT_SECRET=secret_for_oidc_client # If OIDC_AUTH_DISCOVERY_URL is set, the endpoints below can be left empty. # Even if authorization/token endpoints are filled in, discovery still fills jwks_uri or issuer when those are empty, for id_token verification. # OIDC_AUTH_AUTHORIZATION_ENDPOINT=http://127.0.0.1:5556/dex/auth # OIDC_AUTH_TOKEN_ENDPOINT=http://127.0.0.1:5556/dex/token # OIDC_AUTH_USER_INFO_ENDPOINT=http://127.0.0.1:5556/dex/userinfo # OIDC_AUTH_JWKS_URI=http://127.0.0.1:5556/dex/keys # OIDC_AUTH_SCOPES="openid profile email" # Space policy after first-time OIDC auto-provisioning shares WEKNORA_AUTH_DEFAULT_TENANT_MODE with local registration; there is no separate setting. # OIDC_USER_INFO_MAPPING_USER_NAME=name # OIDC_USER_INFO_MAPPING_EMAIL=email # ##################################################################### # G. Agent & sandbox # ##################################################################### # ========== G1. Agent Skills Sandbox ========== # Sandbox backend, credentials, templates, timeouts, and private-network access policy are all maintained # per space under Settings → Sandbox backend. The only deploy-level switch is the Docker backend: off by default # because a local docker.sock is equivalent to root on the host. Set true for a private single-node install, or # have a system admin enable it under Settings → System settings → Network security (takes effect immediately, no restart). WEKNORA_SANDBOX_DOCKER_ENABLED=false # Multi-instance deploys should still set the shared REDIS_ADDR so session→sandbox bindings can be shared. # Custom Skills directory (set after mounting, so you do not need to rebuild the image). # WEKNORA_SKILLS_DIR= # Default timeout for agent LLM calls (seconds, default 120; increase to 300/600 for complex reasoning). # Note: this is the global default; if an agent has its own llm_call_timeout in the database, that wins. # WEKNORA_AGENT_LLM_TIMEOUT=300 # Timeout waiting for human approval of MCP tools (seconds, default 600; also accepts Go durations such as 30s/5m/1h). # WEKNORA_AGENT_TOOL_APPROVAL_TIMEOUT=600 # Whether to allow MCP tools through if human approval fails (true = allow / default fail-close = deny). # WEKNORA_AGENT_TOOL_APPROVAL_FAIL_OPEN= # ##################################################################### # H. Optional integrations # ##################################################################### # ========== H1. Web search (SearXNG / Tavily) ========== # ===== SearXNG (optional self-hosted web search) ===== # Enable with: `docker compose --profile searxng up -d`. # If SEARXNG_SECRET is unset, docker-compose uses a hardcoded default, which is enough for local/loopback deploys; # if you set SEARXNG_BIND=0.0.0.0 and expose the instance on LAN/the public internet, you MUST generate a secret with # openssl rand -hex 32 and set SEARXNG_SECRET explicitly, otherwise anyone can use the default to sign image-proxy URLs. # # Backend access (containerized deploy): in the console, set Provider type to "SearXNG" and # Instance URL to http://searxng:8080; docker compose already injects the searxng hostname into # SSRF_WHITELIST_EXTRA, so no extra config is needed. # # Local development (go run + docker compose -f docker-compose.dev.yml --profile searxng up): # the backend runs on the host and must use the published port; set Instance URL to http://127.0.0.1:8888, # and add 127.0.0.1 to SSRF_WHITELIST below. # # The port listens on 127.0.0.1 only by default, so an instance with limiter:false is not exposed to the LAN. # To open it on the internal network, explicitly set SEARXNG_BIND=0.0.0.0 and harden it yourself. # SEARXNG_PORT=8888 # SEARXNG_BIND=127.0.0.1 # SEARXNG_SECRET= # Tavily Search API Key (optional; enables the Tavily web-search provider). # TAVILY_API_KEY=tvly-your_tavily_api_key # ========== H2. MCP Server (optional; started with --profile full) ========== # API Key the MCP Server uses to call the WeKnora REST API (generate it in the frontend under Settings -> API Keys). # WEKNORA_API_KEY= # Required for HTTP/SSE transport: mcp-server refuses to start without this (clients send it as Authorization: Bearer). # MCP_SERVER_AUTH_TOKEN= # Read timeout when calling WeKnora REST (seconds, default 300). # WEKNORA_CHAT_TIMEOUT=300 # Whether to verify the WeKnora backend TLS certificate (default true; set false for self-signed certs). # WEKNORA_VERIFY_SSL=true # Allow-list of directories that may be uploaded (comma-separated; leave empty to disable the file-upload tool). # MCP_ALLOWED_UPLOAD_DIRS= # ##################################################################### # I. Observability (Langfuse, optional) # ##################################################################### # ========== I1. Langfuse integration (trace chat/embedding/rerank/VLM/ASR model calls and count tokens) ========== # Details: docs/Langfuse集成.md # # Option A: Langfuse Cloud (simplest) # 1) Sign in at https://cloud.langfuse.com and generate an API Key # 2) Fill in PUBLIC_KEY / SECRET_KEY below # 3) docker compose up -d app # # Option B: self-hosted Langfuse (LAN / private network) # 1) Start the self-hosted stack: docker compose --profile langfuse up -d # First start takes ~1-2 minutes for ClickHouse migrations; wait until langfuse-web is healthy # 2) Open http://localhost:3000, register an admin account, and generate an API Key # 3) Uncomment LANGFUSE_HOST=http://langfuse-web:3000 below and fill in the PUBLIC_KEY / SECRET_KEY you just created # 4) docker compose up -d app # # Setting both PUBLIC_KEY and SECRET_KEY enables Langfuse automatically; no explicit switch is required. LANGFUSE_PUBLIC_KEY=pk-lf-xxxxxxxx LANGFUSE_SECRET_KEY=sk-lf-xxxxxxxx LANGFUSE_HOST=http://langfuse-web:3000 # For self-hosted mode use: LANGFUSE_HOST=http://langfuse-web:3000 # Optional: explicit switch (true/false; by default inferred from the keys). # LANGFUSE_ENABLED=true # Optional: version / environment labels for filtering in the Langfuse UI. # LANGFUSE_RELEASE=v0.4.2 # LANGFUSE_ENVIRONMENT=production # Optional: batch reporting and sampling (for high production traffic, raise FLUSH_AT and lower SAMPLE_RATE). # LANGFUSE_FLUSH_AT=15 # LANGFUSE_FLUSH_INTERVAL=3s # LANGFUSE_QUEUE_SIZE=2048 # LANGFUSE_REQUEST_TIMEOUT=10s # LANGFUSE_SAMPLE_RATE=1.0 # LANGFUSE_DEBUG=false # ========== I2. Self-hosted Langfuse stack (only needed with --profile langfuse) ========== # Design: to keep resource use low, the self-hosted Langfuse stack **reuses** WeKnora's existing # - postgres: creates a separate "langfuse" database (one-shot via the langfuse-db-init container) # - redis: uses a separate Redis DB index (default 1; WeKnora uses 0) # The only new long-running containers are 3: langfuse-web, langfuse-worker, langfuse-clickhouse # + 1 dedicated S3: langfuse-minio # + 1 one-shot init: langfuse-db-init # Langfuse Web UI public port. # LANGFUSE_WEB_PORT=3000 # Langfuse-dedicated MinIO ports (avoid clashing with WeKnora's main MinIO on 9000/9001). # LANGFUSE_MINIO_S3_PORT=9100 # LANGFUSE_MINIO_CONSOLE_PORT=9101 # External endpoint for media uploads (browser talks to Langfuse MinIO directly); defaults to the host-mapped 9100 port. # LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9100 # Langfuse database name created in WeKnora-postgres. # LANGFUSE_DB_NAME=langfuse # Redis DB index used in WeKnora-redis (1–15; do not clash with WeKnora's 0). # LANGFUSE_REDIS_DB=1 # ClickHouse / Langfuse MinIO credentials (change these in production). # LANGFUSE_CLICKHOUSE_USER=clickhouse # LANGFUSE_CLICKHOUSE_PASSWORD=clickhouse # LANGFUSE_MINIO_USER=langfuseminio # LANGFUSE_MINIO_PASSWORD=langfuseminiosecret # Langfuse core security fields; you must regenerate these in production: # LANGFUSE_SALT=$(openssl rand -base64 32) # LANGFUSE_ENCRYPTION_KEY=$(openssl rand -hex 32) # LANGFUSE_NEXTAUTH_SECRET=$(openssl rand -base64 32) # LANGFUSE_SALT= # LANGFUSE_ENCRYPTION_KEY= # LANGFUSE_NEXTAUTH_SECRET= # LANGFUSE_NEXTAUTH_URL=http://localhost:3000 # LANGFUSE_TELEMETRY_ENABLED=false # Optional: automate first start (when filled, injects admin + project and skips UI registration). # LANGFUSE_INIT_ORG_ID=WeKnora # LANGFUSE_INIT_ORG_NAME=WeKnora # LANGFUSE_INIT_PROJECT_ID=WeKnora # LANGFUSE_INIT_PROJECT_NAME=WeKnora # LANGFUSE_INIT_PROJECT_PUBLIC_KEY=pk-lf-weknora-init # LANGFUSE_INIT_PROJECT_SECRET_KEY=sk-lf-weknora-init # LANGFUSE_INIT_USER_EMAIL=admin@example.com # LANGFUSE_INIT_USER_NAME=Admin # LANGFUSE_INIT_USER_PASSWORD=change-me-please # ##################################################################### # J. Security & deploy tuning # ##################################################################### # ========== J1. SSRF protection ========== # SSRF validation allow-list (optional). Comma-separated; each entry can be: exact hostname (api.internal), wildcard (*.example.com), # IPv4 (203.0.113.5), IPv6 (2001:db8::1, no brackets), or CIDR (10.0.0.0/8, 2001:db8::/32). # Listed entries bypass normal SSRF rules in URL validation and similar checks; configure carefully in production. # SSRF_WHITELIST=internal.service,*.corp.example,172.16.0.0/12,2001:db8::1,fd00::/8 # # ===== SSRF checks on vector-store addresses ===== # Addresses used when creating/testing vector-store connections via the API go through SSRF checks. Built-in docker-compose # services (qdrant, milvus, weaviate, doris-fe, doris-be, searxng, minio) are already allowed via SSRF_WHITELIST_EXTRA. # If you use an external private address or a blocked port (5432, 9200, etc.), add the host to SSRF_WHITELIST_EXTRA # (note: setting this variable overrides the compose default, so you must re-include the built-in service names), e.g.: # SSRF_WHITELIST_EXTRA=searxng,qdrant,milvus,weaviate,doris-fe,doris-be,minio,my-opensearch.internal # # Doris Stream Load: the FE 307-redirects the request to the BE; docker compose allows # doris-fe and doris-be by default (see SSRF_WHITELIST_EXTRA). # # Local development (go run + docker-compose.dev.yml --profile opensearch): the backend runs on the host, # OpenSearch is reached via the published port http://localhost:9200; before creating that vector store via the API, # add localhost to SSRF_WHITELIST (9200 is a blocked port and localhost is a restricted hostname): # SSRF_WHITELIST=localhost # Allow-list of image hosts whose original URL is kept (optional, comma-separated). Images from these hosts are still downloaded and analyzed # (OCR/captions), but markdown keeps the original URL instead of replacing it with an object-storage provider:// URL. # IMAGE_HOST_KEEP_URL=mineru.internal.example.com # ========== J2. Proxies & concurrency ========== # CIDRs of proxies gin should trust (comma-separated). By default loopback + private ranges are trusted (covers nginx on the container network). # An explicit empty string disables proxy trust (ClientIP is the direct peer). For production behind multiple proxies, set this to the real ranges. # WEKNORA_TRUSTED_PROXIES= # ants goroutine pool size (embedding concurrency; lower it if you hit 429s). CONCURRENCY_POOL_SIZE=5