Files
WeKnora/docker-compose.yml
T
Greenplumwine 439af21f9b feat(feishu-driver): 新增飞书云盘数据源 (#2466)
* feat(datasource): add Feishu/Lark Drive (云盘) connector

Add a new Drive (云盘) data source alongside the existing Feishu wiki
connector, letting users sync documents/files under a user-supplied Drive
folder_token. Shares the feishu connector package (Client/Config/Region/
export/download/error-handling) with the wiki connector; only resource
enumeration and fetch dispatch differ.

Backend:
- types/datasource.go: ConnectorTypeFeishuDrive / ConnectorTypeLarkDrive
- region.go: RegionFeishuDrive / RegionLarkDrive + driveFolderURL
- types.go: driveFile / driveFileListResponse / partialDriveFileListError
  / feishuDriveCursor
- client.go: ListDriveFiles / ListDriveFilesRecursiveFrom (shortcut
  expansion via shortcut_info, visited cycle guard, root-folder rejection)
- drive_connector.go: DriveConnector implementing StreamingConnector
  (FetchStream) + FetchAll + FetchIncremental fallback paths, mirroring
  the wiki connector's three cursor semantics (resume fast-path,
  failure-does-not-advance-cursor, toSyncCursor snapshot), plus
  ListResources / ResolveResourceAncestors (top-down shared traversal,
  best-effort) / fetchDriveFileContent / parseDriveResourceID
- container.go + connector.go: register two Drive instances + metadata

Incremental detection uses modified_time returned directly by the list
API (verified) - no batch_query/metas call needed. Shortcuts are expanded
at list-parse time via shortcut_info; target_type is never "folder".

Frontend:
- DataSourceEditorDialog.vue: connectorDefs for feishu_drive/lark_drive,
  Drive root folder_token input + load button before the lazy-load tree,
  edit-mode prefill from resource_ids
- datasourceIcons.ts: reuse feishu/lark icons
- i18n (zh-CN/en-US/ko-KR/ru-RU): connector labels, descriptions, drive
  input group

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(datasource): refine Feishu Drive connector UI, channel and root folder

A batch of fixes from end-to-end verification of the Feishu/Lark Drive
connector, all aimed at matching the wiki connector's behavior:

Channel (knowledge "source" label):
- Add ChannelFeishuDrive / ChannelLarkDrive constants so Drive docs show
  "飞书云盘" / "Lark 云盘" instead of "unknown", distinct from the wiki
  connector's "飞书" (ChannelFeishu).
- fetchDriveFileContent / appendDriveFileListFailureItems select the
  channel by region via a new driveChannel() helper.
- ingestItem now prefers metadata["channel"] over ds.Type so the
  connector-supplied channel value reaches the knowledge base.
- doc-content.vue channelLabelMap maps feishu_drive/lark_drive.

Frontend Drive picker UX:
- loadDriveRoot persists the new folder_token in edit mode too (previously
  skipped updates in edit mode, so listResources read the old token).
- The folder_token input is now always visible alongside the tree (not a
  switch); the tree area shows a "load a folder first" placeholder until
  the first successful load.
- extractDriveFolderToken accepts a bare token or a Feishu Drive folder
  URL (https://xxx.feishu.cn/drive/folder/<token>) and extracts the token.
- Edit mode auto-loads when a saved folder_token exists, and reveals
  pre-existing selections via revealExistingSelections.
- Classify Drive list errors (403/forbidden, 401/auth, not-found) into
  actionable i18n hints.

Root folder name & selection:
- Add GetDriveFolderMeta (GET /drive/explorer/v2/folder/:folderToken/meta)
  to resolve the root folder's human-readable name; the list API only
  returns children. Best-effort, falls back to the token.
- Fix root folder ExternalID to be the bare rootFolderToken (no ":token"
  suffix) so it matches the saved resource_id form and the direct
  children's ParentID, which previously broke selection matching on edit.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(datasource): sync a single selected Drive file without folder error

When a user selects a single file (not a folder) in the Drive picker, the
resourceID is "rootFolderToken:fileToken". FetchStream/FetchAll/
FetchIncremental previously passed fileToken straight to
ListDriveFilesRecursiveFrom, which expects a folder token - the Feishu
list API returns 1061002 (params error) for a file token, failing the
whole sync with "all fetched items failed".

The wiki connector resolves a single selected node via GetWikiNode; Drive
has no single-file meta API (folder meta only works on folders, and
metas/batch_query requires doc_type as input - a chicken-and-egg).
Instead, listDriveFilesForResource walks the root folder subtree (the
file's parent) and filters to the selected fileToken, yielding the full
driveFile (type/name/modified_time) needed for fetchDriveFileContent.

A sub-folder selection (fileToken is itself a folder) still walks that
sub-folder directly - ListDriveFilesRecursiveFrom accepts a folder token,
so no filtering is needed there.

Co-Authored-By: Claude <noreply@anthropic.com>

* ci: add build-test-images workflow for tar.gz artifact export

Manual-only workflow_dispatch that builds linux/amd64 Docker images for
the current branch and exports them as .tar.gz artifacts downloadable
from the Actions run page. No Docker Hub push, no secrets required.

- build-ui: builds frontend dist, builds weknora-ui image, exports tar.gz
- build-app: builds weknora-app image (CGO + duckdb), exports tar.gz
- Input choice: build both / app only / ui only
- Artifacts retained 7 days

Co-Authored-By: Claude <noreply@anthropic.com>

* ci: add build-test-images workflow for tar.gz artifact export

Manual-only workflow_dispatch that builds linux/amd64 Docker images for a
selected branch and exports them as .tar.gz artifacts downloadable from the
Actions run page. No Docker Hub push, no secrets required.

- build-ui: builds frontend dist, builds weknora-ui image, exports tar.gz
- build-app: builds weknora-app image (CGO + duckdb), exports tar.gz
- Input choice: build both / app only / ui only
- Artifacts retained 7 days

Placed on the default branch so the workflow appears in the Actions list;
trigger it with the branch you want to build (e.g. feat/datasource-feishu-drive).

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(datasource): add feishu shared-blocks extraction design spec

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(datasource): add feishu shared-blocks implementation plan

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(datasource): sync feishu drive docx via blocks API with export fallback

- Extract wiki/drive shared helpers from connector.go into shared.go
- Make fetchDocxWithBlocks a package-level function taking docxFetchInput
- Drive docx now renders via blocks API (Markdown + attachment/image
  sub-items), falling back to the export API on blocks errors or empty
  renders, mirroring the wiki connector
- fetchDriveFileContent returns item slices and takes multimodalEnabled

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(frontend): label feishu drive knowledge as 飞书云盘 in list

The knowledge list source column (DocumentListView.getSourceInfo) only
whitelisted feishu/notion/yuque/etc., so feishu_drive/lark_drive docs
fell through to the default "上传" label while the wiki connector's
"feishu" showed "飞书". Map feishu_drive → 飞书云盘 and lark_drive →
Lark 云盘 (i18n keys already existed), and add feishu_drive to the
source filter options in KnowledgeBase.vue.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(frontend): polish feishu drive resource-step UI in datasource editor

- Replace the two stacked t-alert banners under the folder_token input:
  shareHint becomes an always-visible form-desc line, rootNotSupportedHint
  moves into a help-circle tooltip next to the label.
- folder_token is now required: red asterisk on the label, inline error
  status + tips on the input (replacing the global MessagePlugin) when
  empty on 加载/下一步, cleared on input.
- Step 2 选择范围 no longer scrolls as a whole: the token input stays
  fixed and the resource region (placeholder / loading / empty / tree)
  fills the remaining drawer height, with the tree scrolling internally.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(datasource): add feishu drive datasource integration guide

User-facing setup guide for feishu_drive/lark_drive: app creation,
required scopes (drive:drive:readonly / drive:export:readonly /
docx:document:readonly), sharing the folder to the app's group, the
four-step wizard, supported file types, sync semantics and FAQ.
Also clarify in extractDriveFolderToken's comment that pasted folder
URLs are matched by path and work for Lark links too.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(datasource): unify feishu wiki/drive sync into a single generic streaming engine

Extract a single generic streaming sync engine (engine.go) shared by the wiki Connector and the Drive DriveConnector. Per-connector differences (node type, listing API, edit-time field, cursor wire format, fetch dispatch, log tag) are isolated behind the NodeOps[N] adapter; FetchAll/FetchIncremental become thin shells over the same engine.

Deliberate behaviour changes (design §2.4): the #2136 failure-doesn't-advance-cursor fix now applies to FetchIncremental too (previously it advanced the cursor before fetching, permanently skipping a node on a transient export failure); logs use 'stream progress/summary' uniformly and the FetchIncremental path gains per-100 progress + tally summary logs.

Structural prep for package split (design §3.1): anonymous Data structs in types.go/blocks.go are named so tests can construct wire types across packages.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(datasource): split feishu connector into core/wiki/drive packages

Move shared code (Client, blocks, markdown, types, region, engine, shared helpers) into feishu/core; wiki and drive connectors into their own packages. Export core symbols (capitalize) so wiki/drive can reference them; wiki/drive import core and prefix cross-package references.

container.go registration updated to import core/wiki/drive (NewConnector from wiki, NewDriveConnector from drive, Region* from core).

Tests are NOT migrated yet (left in feishu/ root, package feishu) and will fail to compile until moved + adapted in a follow-up commit. Source builds clean (go build ./...feishu/... passes).

Co-Authored-By: Claude <noreply@anthropic.com>

* test(datasource): migrate feishu tests to core/wiki/drive packages

Move 14 test files to their subpackages per design §4. Adapt package declarations, imports, and capitalized core symbol references. Add TestMain (SSRF whitelist) to core and drive. Add local helpers (writeJSON, makeConfig, txt, blk constructors, recordingHandler) where cross-package sharing was not possible. Fix engine.go log wording (summary) clobbered by the capitalization script.

All tests pass: core 4s, wiki 13s, drive 3s.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(datasource): reduce feishu core export surface; add drive cursor round-trip test

Lower-case 87 core symbols that are not referenced cross-package back to unexported (BlocksToMarkdown, CellToString, response types, BlockType constants, etc.), shrinking core's API surface to only what wiki/drive actually use. Restore NodeOps.Fetch capitalization (interface method must match wikiOps/driveOps implementations).

Add TestDriveCursorRoundTrip (design §3.2): driveOps.EncodeCursor/DecodeCursorTimes were new code with no direct test.

All tests pass: core 3.5s, wiki 13s, drive 3s.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(datasource): repair feishu cleanup fallout

Restore core.RegionLarkDrive (cleanup lower-cased it; only container.go references it, outside wiki/drive scan). Restore wiki/connector_realapi_test.go local collectHandler type (cleanup wrongly prefixed core.). Apply gofumpt to helpers_test.go. Add open.feishu.cn/open.larksuite.com to test SSRF whitelist: BaseURL-default tests call ParseFeishuConfig which SSRF-validates the resolved hostname; under a proxy open.feishu.cn resolves to 198.18.0.0/15 restricted range.

All tests pass: core 3.2s, wiki 13s, drive 2.6s. make build-lite passes.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(frontend): feishu drive data source UI and i18n

Add Drive folder/token picker strings and data source editor entries for the feishu_drive/lark_drive connectors across en-US/zh-CN/ko-KR/ru-RU.

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: add trellis journal merge=union rule

Append-only developer journals merge cleanly across parallel sessions; task state lives in task.json, not the journal.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(datasource): rename feishu/drive drive_connector.go to connector.go

Aligns naming with feishu/wiki subpackage (connector.go).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(frontend): allow data:image base64 URIs in sanitized markdown

DOMPurify's ALLOWED_URI_REGEXP rejected data: URIs, so <img src="data:image/...">
in uploaded markdown files had their src stripped during sanitizeHTML — the file
preview showed broken images while the chunk view (minio:// via
hydrateProtectedFileImages) worked. Add data:image/ to the allowed URI scheme so
base64 images render in preview and in LLM answers that embed base64 images.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(datasource): add FEISHU_DOCX_PARSE_MODE for feishu docx parse path

The blocks API path renders image blocks as empty ![图片]() placeholders and
fans images into separate knowledge items, breaking image↔document association
across retrieval/wiki/agent. FEISHU_DOCX_PARSE_MODE=export forces the async
export API (.docx binary -> docreader), so images are parsed inline and bound to
the parent document via parent_chunk_id (same as a regular docx upload). Default
(unset / "blocks") keeps the existing blocks-first behaviour. Affects both wiki
and drive connectors since they share FetchDocxWithBlocks.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(datasource): default FEISHU_DOCX_PARSE_MODE to export

Switch the feishu docx parse default from blocks to export so embedded
images are parsed inline and bound to the parent document via
parent_chunk_id (retrieval / wiki / agent all associate images). Operators
who need the blocks behaviour (faster, keeps docx attachments, no
docreader round-trip) set FEISHU_DOCX_PARSE_MODE=blocks.

- shared.go: default unset -> export; update comment to match
- .env.example / docker-compose.yml: default export
- tests: pin FEISHU_DOCX_PARSE_MODE=blocks on blocks-behaviour tests so
  they don't break under the new default

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(datasource): document FEISHU_DOCX_PARSE_MODE and refresh feishu source layout

- 飞书云盘接入说明: add section on docx parse mode (export default vs
  blocks), permission differences, export tradeoffs
- 数据源导入开发: add env var + mode comparison
- 数据源导入开发文档: rewrite content fetch flow (blocks/export paths),
  add pros/cons table, fix source-file table to core/wiki/drive layout

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: 修复合并main后丢失新增的国际化问题

* fix(i18n): 修复误删的国际化内容

* docs: 删除误提交的文件

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-05 19:39:49 +08:00

1029 lines
47 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
services:
frontend:
image: wechatopenai/weknora-ui:${WEKNORA_VERSION:-latest}
# Requires frontend/dist — run ./scripts/build_frontend_dist.sh before --build
build:
context: ./frontend
container_name: WeKnora-frontend
ports:
- "${FRONTEND_PORT:-80}:80"
environment:
- MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-50}
- APP_HOST=${APP_HOST:-app}
# APP_BACKEND_PORT: the port NGINX proxies to (default 8080).
# For local deployment this is the App container's listening port, independent of host-mapped APP_PORT.
# For remote deployment, set this to the remote App's service port.
- APP_PORT=${APP_BACKEND_PORT:-8080}
- APP_SCHEME=${APP_SCHEME:-http}
# NOTE: If using a remote App backend, comment out or remove the depends_on
# block below and set APP_HOST/APP_BACKEND_PORT/APP_SCHEME in your .env file.
depends_on:
app:
condition: service_healthy
networks:
- WeKnora-network
restart: unless-stopped
app:
image: wechatopenai/weknora-app:${WEKNORA_VERSION:-latest}
build:
context: .
dockerfile: docker/Dockerfile.app
args:
- APK_MIRROR_ARG=${APK_MIRROR_ARG:-}
container_name: WeKnora-app
ports:
- "${APP_PORT:-8080}:8080"
volumes:
- data-files:/data/files
- docreader-tmp:/tmp/docreader:ro
- ./config/config.yaml:/app/config/config.yaml
# Optional: mount custom skills directory (allows adding skills without rebuilding image)
- ./skills/preloaded:/app/skills/preloaded
# Optional: declarative built-in models. Copy config/builtin_models.yaml.example
# to config/builtin_models.yaml, edit it for your deployment, then
# uncomment the line below. See docs/BUILTIN_MODELS.md.
# - ./config/builtin_models.yaml:/app/config/builtin_models.yaml:ro
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
# Source .env so deployment-specific variables (e.g. those referenced by
# config/builtin_models.yaml via ${ENV}, or any future feature) reach the
# app container without having to be enumerated under `environment:`.
#
# NOTE: the array form below is used instead of the map form
# (env_file: - path: .env, required: false) because the map syntax
# requires Docker Compose v2.24+ (Jan 2024) and would break parsing on
# older deploys. The trade-off is that Compose errors out if .env is
# absent — see scripts/start_all.sh for the pre-flight touch that keeps
# fresh clones working.
env_file:
- .env
environment:
# ========== 日志 ==========
# 日志级别,默认debug
- LOG_LEVEL=${LOG_LEVEL:-debug}
# 日志文件路径(留空则只输出 stdout);LLM_DEBUG_LOG 开启时同目录写 llm_debug.log
- LOG_PATH=${LOG_PATH:-}
# 自定义日志格式模板(留空用内置默认格式)
- LOG_FORMAT=${LOG_FORMAT:-}
# LLM 调试日志:true 写 LOG_PATH 同目录 llm_debug.log,或指定路径;留空关闭
- LLM_DEBUG_LOG=${LLM_DEBUG_LOG:-false}
# ========== 运行时基础 ==========
# gin mod
# 可选值: debug(开发模式,有详细日志), release(生产模式,禁用Swagger文档)
- GIN_MODE=${GIN_MODE:-release}
# 时区,默认为Asia/Shanghai
- TZ=${TZ:-Asia/Shanghai}
# 文档处理语言(问题/摘要生成)。优先级:此环境变量(部署级覆盖,非空即生效)
# > Accept-Language 请求头 > 内置默认值 (zh-CN)。留空则跟随请求方 Accept-Language。
- WEKNORA_LANGUAGE=${WEKNORA_LANGUAGE:-}
# gin 信任的代理 CIDR(逗号分隔);显式设为空字符串则禁用代理信任。
# 默认信任 loopback + 私网段(容器网络内 nginx 已覆盖)
# - WEKNORA_TRUSTED_PROXIES=${WEKNORA_TRUSTED_PROXIES:-}
# 启动时自动执行数据库迁移;设为 false 禁用,默认启用
- AUTO_MIGRATE=${AUTO_MIGRATE:-true}
# IM 渠道图片/文件外链的外部可达 URL。IM 显示知识库图片需二选一:
# 存储后端公网可达(如 MINIO_ENDPOINT 设为公网),或设本变量走 <APP_EXTERNAL_URL>/r/<token>(需 nginx 代理 /r/)。
# 默认 MinIO 内网部署 / local 后端需设本变量。
# - APP_EXTERNAL_URL=${APP_EXTERNAL_URL:-}
# 前端外部 origin,用于邀请链接等绝对 URL(留空走 host-relative
- FRONTEND_BASE_URL=${FRONTEND_BASE_URL:-}
# ========== 对象存储 - 腾讯云 COS ==========
- COS_SECRET_ID=${COS_SECRET_ID:-}
- COS_SECRET_KEY=${COS_SECRET_KEY:-}
- COS_REGION=${COS_REGION:-}
- COS_BUCKET_NAME=${COS_BUCKET_NAME:-}
- COS_APP_ID=${COS_APP_ID:-}
- COS_PATH_PREFIX=${COS_PATH_PREFIX:-}
- COS_TEMP_BUCKET_NAME=${COS_TEMP_BUCKET_NAME:-}
- COS_TEMP_REGION=${COS_TEMP_REGION:-}
# 认证 / 空间 / RBAC / 审计
# 禁止新用户注册(生产建议 true)
- DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-false}
# 公开注册后的默认空间策略:create_personal(默认,自动创建个人空间)/ tenantless
- WEKNORA_AUTH_DEFAULT_TENANT_MODE=${WEKNORA_AUTH_DEFAULT_TENANT_MODE:-create_personal}
# 是否允许普通用户登录后主动创建空间(默认 true;false 仅能邀请加入,超管不受影响)
- WEKNORA_TENANT_SELF_SERVICE_CREATION_ENABLED=${WEKNORA_TENANT_SELF_SERVICE_CREATION_ENABLED:-true}
# 启用跨空间访问(需配合用户的 CanAccessAllTenants 权限;默认 false
- WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS=${WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS:-false}
# 新建空间默认存储配额(GB,默认 10)
- WEKNORA_TENANT_DEFAULT_STORAGE_QUOTA_GB=${WEKNORA_TENANT_DEFAULT_STORAGE_QUOTA_GB:-10}
# 空间邀请链接 TTLGo duration,默认 168h=7 天)
- WEKNORA_INVITATION_TTL=${WEKNORA_INVITATION_TTL:-168h}
# 审计日志保留天数(0 禁用清理,默认 90)
- WEKNORA_AUDIT_RETENTION_DAYS=${WEKNORA_AUDIT_RETENTION_DAYS:-90}
# 数据库设置
- DB_DRIVER=${DB_DRIVER:-postgres}
- DB_HOST=${DB_HOST:-postgres}
- DB_PORT=${DB_PORT:-5432}
- DB_USER=${DB_USER:-}
- DB_PASSWORD=${DB_PASSWORD:-}
- DB_NAME=${DB_NAME:-}
# SQLite 驱动时使用(DB_DRIVER=sqlite);postgres 驱动忽略
# - DB_PATH=${DB_PATH:-}
# ========== Langfuse (optional observability) ==========
# Enabled automatically when LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY are set.
# See docs/Langfuse集成.md for the full list of tuning knobs.
- LANGFUSE_ENABLED=${LANGFUSE_ENABLED:-}
- LANGFUSE_HOST=${LANGFUSE_HOST:-https://cloud.langfuse.com}
- LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY:-}
- LANGFUSE_SECRET_KEY=${LANGFUSE_SECRET_KEY:-}
- LANGFUSE_RELEASE=${LANGFUSE_RELEASE:-}
- LANGFUSE_ENVIRONMENT=${LANGFUSE_ENVIRONMENT:-}
- LANGFUSE_FLUSH_AT=${LANGFUSE_FLUSH_AT:-}
- LANGFUSE_FLUSH_INTERVAL=${LANGFUSE_FLUSH_INTERVAL:-}
- LANGFUSE_QUEUE_SIZE=${LANGFUSE_QUEUE_SIZE:-}
- LANGFUSE_REQUEST_TIMEOUT=${LANGFUSE_REQUEST_TIMEOUT:-}
- LANGFUSE_SAMPLE_RATE=${LANGFUSE_SAMPLE_RATE:-}
- LANGFUSE_DEBUG=${LANGFUSE_DEBUG:-}
# 向量库 / 检索引擎
- RETRIEVE_DRIVER=${RETRIEVE_DRIVER:-postgres}
- ELASTICSEARCH_ADDR=${ELASTICSEARCH_ADDR:-}
- ELASTICSEARCH_USERNAME=${ELASTICSEARCH_USERNAME:-}
- ELASTICSEARCH_PASSWORD=${ELASTICSEARCH_PASSWORD:-}
- ELASTICSEARCH_INDEX=${ELASTICSEARCH_INDEX:-}
- QDRANT_HOST=${QDRANT_HOST:-qdrant}
- QDRANT_PORT=${QDRANT_PORT:-6334}
- QDRANT_COLLECTION=${QDRANT_COLLECTION:-weknora_embeddings}
- QDRANT_API_KEY=${QDRANT_API_KEY:-}
- QDRANT_USE_TLS=${QDRANT_USE_TLS:-false}
- MILVUS_ADDRESS=${MILVUS_ADDRESS:-milvus:19530}
- MILVUS_COLLECTION=${MILVUS_COLLECTION:-weknora_embeddings}
- MILVUS_METRIC_TYPE=${MILVUS_METRIC_TYPE:-IP}
- MILVUS_USERNAME=${MILVUS_USERNAME:-}
- MILVUS_PASSWORD=${MILVUS_PASSWORD:-}
- MILVUS_DB_NAME=${MILVUS_DB_NAME:-}
# 多向量库并行检索超时(秒,RETRIEVE_DRIVER 含多个驱动时生效)
- MULTI_STORE_RETRIEVE_TIMEOUT_SEC=${MULTI_STORE_RETRIEVE_TIMEOUT_SEC:-}
# ========== OpenSearchRETRIEVE_DRIVER=opensearch==========
- OPENSEARCH_ADDR=${OPENSEARCH_ADDR:-}
- OPENSEARCH_USERNAME=${OPENSEARCH_USERNAME:-}
- OPENSEARCH_PASSWORD=${OPENSEARCH_PASSWORD:-}
- OPENSEARCH_INSECURE_SKIP_VERIFY=${OPENSEARCH_INSECURE_SKIP_VERIFY:-false}
- OPENSEARCH_INDEX=${OPENSEARCH_INDEX:-}
# ========== 腾讯云 VectorDBRETRIEVE_DRIVER=tencent_vectordb==========
- TENCENT_VECTORDB_ADDR=${TENCENT_VECTORDB_ADDR:-}
- TENCENT_VECTORDB_USERNAME=${TENCENT_VECTORDB_USERNAME:-}
- TENCENT_VECTORDB_API_KEY=${TENCENT_VECTORDB_API_KEY:-}
- TENCENT_VECTORDB_DATABASE=${TENCENT_VECTORDB_DATABASE:-}
- TENCENT_VECTORDB_COLLECTION=${TENCENT_VECTORDB_COLLECTION:-}
- TENCENT_VECTORDB_REPLICA_NUMBER=${TENCENT_VECTORDB_REPLICA_NUMBER:-}
# Docreader 解析调优
- DOCREADER_ADDR=${DOCREADER_ADDR:-docreader:50051}
- DOCREADER_TRANSPORT=${DOCREADER_TRANSPORT:-grpc}
# docreader gRPC TLS / 认证(客户端侧)
- GRPC_TLS_ENABLED=${GRPC_TLS_ENABLED:-false}
- GRPC_TLS_CERT=${GRPC_TLS_CERT:-}
- GRPC_TLS_KEY=${GRPC_TLS_KEY:-}
- GRPC_TLS_CA=${GRPC_TLS_CA:-}
- GRPC_TLS_SERVER_NAME=${GRPC_TLS_SERVER_NAME:-}
- GRPC_AUTH_TOKEN=${GRPC_AUTH_TOKEN:-}
- WEAVIATE_HOST=${WEAVIATE_HOST:-}
- WEAVIATE_GRPC_ADDRESS=${WEAVIATE_GRPC_ADDRESS:-}
- WEAVIATE_SCHEME=${WEAVIATE_SCHEME:-}
- WEAVIATE_AUTH_ENABLED=${WEAVIATE_AUTH_ENABLED:-}
- WEAVIATE_API_KEY=${WEAVIATE_API_KEY:-}
- DORIS_ADDR=${DORIS_ADDR:-}
- DORIS_HTTP_PORT=${DORIS_HTTP_PORT:-}
- DORIS_DATABASE=${DORIS_DATABASE:-}
- DORIS_USERNAME=${DORIS_USERNAME:-}
- DORIS_PASSWORD=${DORIS_PASSWORD:-}
- DORIS_TABLE_PREFIX=${DORIS_TABLE_PREFIX:-}
- DORIS_COMPAT_MODE=${DORIS_COMPAT_MODE:-}
# ========== 文件存储 ==========
- STORAGE_TYPE=${STORAGE_TYPE:-local}
# 允许用户使用的存储类型白名单(逗号分隔,留空允许全部)
- STORAGE_ALLOW_LIST=${STORAGE_ALLOW_LIST:-}
- LOCAL_STORAGE_BASE_DIR=${LOCAL_STORAGE_BASE_DIR:-/data/files}
# 本地存储路径前缀(legacy 回填用,一般留空)
- LOCAL_STORAGE_PATH_PREFIX=${LOCAL_STORAGE_PATH_PREFIX:-}
- AUTO_RECOVER_DIRTY=${AUTO_RECOVER_DIRTY:-true}
# ----- MinIOSTORAGE_TYPE=minio-----
- MINIO_ENDPOINT=${MINIO_ENDPOINT:-minio:9000}
- MINIO_ACCESS_KEY_ID=${MINIO_ACCESS_KEY_ID:-minioadmin}
- MINIO_SECRET_ACCESS_KEY=${MINIO_SECRET_ACCESS_KEY:-minioadmin}
- MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME:-}
- MINIO_PATH_PREFIX=${MINIO_PATH_PREFIX:-}
- MINIO_USE_SSL=${MINIO_USE_SSL:-false}
# ----- 火山引擎 TOSSTORAGE_TYPE=tos-----
- TOS_ENDPOINT=${TOS_ENDPOINT:-}
- TOS_REGION=${TOS_REGION:-}
- TOS_ACCESS_KEY=${TOS_ACCESS_KEY:-}
- TOS_SECRET_KEY=${TOS_SECRET_KEY:-}
- TOS_BUCKET_NAME=${TOS_BUCKET_NAME:-}
- TOS_PATH_PREFIX=${TOS_PATH_PREFIX:-}
- TOS_TEMP_BUCKET_NAME=${TOS_TEMP_BUCKET_NAME:-}
- TOS_TEMP_REGION=${TOS_TEMP_REGION:-}
# ----- AWS S3STORAGE_TYPE=s3-----
- S3_ENDPOINT=${S3_ENDPOINT:-}
- S3_REGION=${S3_REGION:-}
- S3_ACCESS_KEY=${S3_ACCESS_KEY:-}
- S3_SECRET_KEY=${S3_SECRET_KEY:-}
- S3_BUCKET_NAME=${S3_BUCKET_NAME:-}
- S3_PATH_PREFIX=${S3_PATH_PREFIX:-weknora/}
- S3_USE_SSL=${S3_USE_SSL:-true}
- S3_FORCE_PATH_STYLE=${S3_FORCE_PATH_STYLE:-false}
# ----- 华为云 OBSSTORAGE_TYPE=obs-----
- OBS_ENDPOINT=${OBS_ENDPOINT:-}
- OBS_REGION=${OBS_REGION:-}
- OBS_ACCESS_KEY=${OBS_ACCESS_KEY:-}
- OBS_SECRET_KEY=${OBS_SECRET_KEY:-}
- OBS_BUCKET_NAME=${OBS_BUCKET_NAME:-}
- OBS_PATH_PREFIX=${OBS_PATH_PREFIX:-weknora/}
- OBS_USE_SSL=${OBS_USE_SSL:-true}
- OBS_PROXY_DOMAIN=${OBS_PROXY_DOMAIN:-}
# ----- 阿里云 OSSSTORAGE_TYPE=oss-----
- OSS_ENDPOINT=${OSS_ENDPOINT:-}
- OSS_REGION=${OSS_REGION:-}
- OSS_ACCESS_KEY=${OSS_ACCESS_KEY:-}
- OSS_SECRET_KEY=${OSS_SECRET_KEY:-}
- OSS_BUCKET_NAME=${OSS_BUCKET_NAME:-}
- OSS_PATH_PREFIX=${OSS_PATH_PREFIX:-weknora/}
- OSS_TEMP_BUCKET_NAME=${OSS_TEMP_BUCKET_NAME:-}
- OSS_TEMP_REGION=${OSS_TEMP_REGION:-}
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
# Ollama 不可用时仅告警不阻断(true 时生效)
- OLLAMA_OPTIONAL=${OLLAMA_OPTIONAL:-true}
# 批量 embedding 大小
- BATCH_EMBED_SIZE=${BATCH_EMBED_SIZE:-}
# VLM 单次 HTTP 请求超时(秒,默认 180)
- VLM_HTTP_TIMEOUT_SECONDS=${VLM_HTTP_TIMEOUT_SECONDS:-}
# LLM 流原始转储(排查上下文问题;1 启用,默认目录 ~/.weknora/investigate/llm-stream
- WEKNORA_LLM_STREAM_RAW_DUMP=${WEKNORA_LLM_STREAM_RAW_DUMP:-}
- WEKNORA_LLM_STREAM_RAW_DUMP_DIR=${WEKNORA_LLM_STREAM_RAW_DUMP_DIR:-}
- STREAM_MANAGER_TYPE=${STREAM_MANAGER_TYPE:-}
- REDIS_ADDR=${REDIS_ADDR:-redis:6379}
- REDIS_USERNAME=${REDIS_USERNAME:-}
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
- REDIS_DB=${REDIS_DB:-}
- REDIS_PREFIX=${REDIS_PREFIX:-}
# Redis TLS(托管 Redis 加密传输;REDIS_USE_TLS=true 时生效,详见 .env.example
- REDIS_TLS_SERVER_NAME=${REDIS_TLS_SERVER_NAME:-}
- REDIS_TLS_INSECURE_SKIP_VERIFY=${REDIS_TLS_INSECURE_SKIP_VERIFY:-}
# Redis 频道命名空间后缀(多部署共享同一 Redis 时隔离用)
- WEKNORA_REDIS_NAMESPACE=${WEKNORA_REDIS_NAMESPACE:-}
# Asynq 客户端 Redis 读写超时(毫秒,默认 500)
- WEKNORA_REDIS_OP_TIMEOUT_MS=${WEKNORA_REDIS_OP_TIMEOUT_MS:-}
# 知识图谱唯一开关是 NEO4J_ENABLE(见下方)。ENABLE_GRAPH_RAG 自 v0.1.6 起已被
# NEO4J_ENABLE 取代,Go 主应用不再读取,此处不再透传。
- NEO4J_ENABLE=${NEO4J_ENABLE:-}
- NEO4J_URI=${NEO4J_URI:-bolt://neo4j:7687}
- NEO4J_USERNAME=${NEO4J_USERNAME:-neo4j}
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
# 数据库中 API Key 等敏感字段落盘加密的 AES-256 主密钥(必须为 32 字节)。
# 加密范围:tenants.api_key、模型 API key、向量库凭证、web search provider key、
# WeKnoraCloud.AppSecret 等。缺失或长度不对时,已加密字段无法解密(UI 显示为空,
# 需重新填写)。生产环境必须显式设置且妥善保管,丢失则所有加密数据不可恢复。
# TENANT_AES_KEY / CRYPTO_MASTER_KEY / CRYPTO_SALT 已废弃:v0.4.0 起加密统一
# 改用 SYSTEM_AES_KEY,Go 主应用不再读取这三个变量。)
- SYSTEM_AES_KEY=${SYSTEM_AES_KEY:-}
- SSRF_WHITELIST=${SSRF_WHITELIST:-}
# 保留原始 URL 的图片域名白名单(逗号分隔,不替换为 provider://
- IMAGE_HOST_KEEP_URL=${IMAGE_HOST_KEEP_URL:-}
# Always allow the optional searxng sidecar and the bundled vector-store
# service hostnames (compose service names). Vector-store create/test now
# runs SSRF validation on user-supplied addresses, which would otherwise
# reject these private docker-network hosts; whitelisting them keeps the
# out-of-box experience. Merged on top of SSRF_WHITELIST so user
# overrides don't clobber it. (postgres is intentionally NOT listed: it
# is not a user-registerable vector store, and whitelisting the app's own
# DB host would skip the 5432 port block.)
# doris-be: Stream Load follows FE→BE redirects that must receive Basic
# credentials on whitelisted BE hosts.
- SSRF_WHITELIST_EXTRA=${SSRF_WHITELIST_EXTRA:-searxng,qdrant,milvus,weaviate,doris-fe,doris-be}
- CONCURRENCY_POOL_SIZE=${CONCURRENCY_POOL_SIZE:-5}
- JWT_SECRET=${JWT_SECRET:-}
# File size limit (in MB)
- MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-50}
# 文档处理任务总超时(Go duration,默认 2h
- WEKNORA_DOCUMENT_PROCESS_TIMEOUT=${WEKNORA_DOCUMENT_PROCESS_TIMEOUT:-}
# 单次 DocReader RPC 超时(默认 30m,须小于上一项)
- WEKNORA_DOCREADER_CALL_TIMEOUT=${WEKNORA_DOCREADER_CALL_TIMEOUT:-}
# 知识库巡检回收(回收卡在 processing 的脏数据;false 禁用,不建议)
- WEKNORA_HOUSEKEEPING_ENABLED=${WEKNORA_HOUSEKEEPING_ENABLED:-}
# 自定义 Skills 目录(挂载后指定,免重建镜像)
- WEKNORA_SKILLS_DIR=${WEKNORA_SKILLS_DIR:-}
- WEKNORA_CHAT_ATTACHMENT_TTL_HOURS=${WEKNORA_CHAT_ATTACHMENT_TTL_HOURS:-24}
- WEKNORA_CHAT_ATTACHMENT_WAIT_TIMEOUT_SEC=${WEKNORA_CHAT_ATTACHMENT_WAIT_TIMEOUT_SEC:-60}
- WEKNORA_CHAT_ATTACHMENT_OCR_CONCURRENCY=${WEKNORA_CHAT_ATTACHMENT_OCR_CONCURRENCY:-8}
- WEKNORA_CHAT_ATTACHMENT_OCR_MAX_PAGES=${WEKNORA_CHAT_ATTACHMENT_OCR_MAX_PAGES:-8}
# Agent Skills Sandbox
- WEKNORA_SANDBOX_MODE=${WEKNORA_SANDBOX_MODE:-docker}
- WEKNORA_SANDBOX_TIMEOUT=${WEKNORA_SANDBOX_TIMEOUT:-60}
- WEKNORA_SANDBOX_DOCKER_IMAGE=${WEKNORA_SANDBOX_DOCKER_IMAGE:-wechatopenai/weknora-sandbox:${WEKNORA_VERSION:-latest}}
# Agent LLM call timeout
- WEKNORA_AGENT_LLM_TIMEOUT=${WEKNORA_AGENT_LLM_TIMEOUT:-}
- WEKNORA_AGENT_TOOL_APPROVAL_TIMEOUT=${WEKNORA_AGENT_TOOL_APPROVAL_TIMEOUT:-}
# MCP 工具人工审批失败时是否放行(true 放行 / 默认 fail-close 拒绝)
- WEKNORA_AGENT_TOOL_APPROVAL_FAIL_OPEN=${WEKNORA_AGENT_TOOL_APPROVAL_FAIL_OPEN:-}
# Tenant / RBAC(详见 docs/RBAC说明.md 与 .env.example 注释)
# - WEKNORA_TENANT_ENABLE_RBAC: 是否启用空间角色强制鉴权(true / false),默认 true
# - WEKNORA_TENANT_MAX_OWNED_PER_USER: 单个非超管自助创建空间的上限
# >0 强制限额;=0 走 handler 默认;<0 关闭限额(不建议共享部署使用)
# - WEKNORA_TENANT_AUTO_CREATE_API_KEY: 创建空间时自动生成 full_access API Key 并在响应中返回 token
# (兼容旧版默认下发 API Key 行为,破坏性变更回退开关;true / false,默认 false
- WEKNORA_TENANT_ENABLE_RBAC=${WEKNORA_TENANT_ENABLE_RBAC:-}
- WEKNORA_TENANT_MAX_OWNED_PER_USER=${WEKNORA_TENANT_MAX_OWNED_PER_USER:-}
- WEKNORA_TENANT_AUTO_CREATE_API_KEY=${WEKNORA_TENANT_AUTO_CREATE_API_KEY:-}
- WEKNORA_ASYNQ_CORE_CONCURRENCY=${WEKNORA_ASYNQ_CORE_CONCURRENCY:-8}
- WEKNORA_ASYNQ_POSTPROCESS_CONCURRENCY=${WEKNORA_ASYNQ_POSTPROCESS_CONCURRENCY:-2}
- WEKNORA_ASYNQ_ENRICHMENT_CONCURRENCY=${WEKNORA_ASYNQ_ENRICHMENT_CONCURRENCY:-12}
- WEKNORA_ASYNQ_MAINTENANCE_CONCURRENCY=${WEKNORA_ASYNQ_MAINTENANCE_CONCURRENCY:-4}
- WEKNORA_ASYNQ_SHARED_CONCURRENCY=${WEKNORA_ASYNQ_SHARED_CONCURRENCY:-6}
- WEKNORA_WIKI_ASYNQ_CONCURRENCY=${WEKNORA_WIKI_ASYNQ_CONCURRENCY:-8}
- WEKNORA_MODEL_MAX_CONCURRENCY=${WEKNORA_MODEL_MAX_CONCURRENCY:-32}
- APK_MIRROR_ARG=${APK_MIRROR_ARG:-}
- WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL=${WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL:-}
# ========== OIDC 认证(可选,OIDC_AUTH_ENABLE=true 启用,详见 .env.example==========
- OIDC_AUTH_ENABLE=${OIDC_AUTH_ENABLE:-false}
- OIDC_AUTH_ISSUER_URL=${OIDC_AUTH_ISSUER_URL:-}
- OIDC_AUTH_DISCOVERY_URL=${OIDC_AUTH_DISCOVERY_URL:-}
- OIDC_AUTH_PROVIDER_DISPLAY_NAME=${OIDC_AUTH_PROVIDER_DISPLAY_NAME:-}
- OIDC_AUTH_CLIENT_ID=${OIDC_AUTH_CLIENT_ID:-}
- OIDC_AUTH_CLIENT_SECRET=${OIDC_AUTH_CLIENT_SECRET:-}
- OIDC_AUTH_AUTHORIZATION_ENDPOINT=${OIDC_AUTH_AUTHORIZATION_ENDPOINT:-}
- OIDC_AUTH_TOKEN_ENDPOINT=${OIDC_AUTH_TOKEN_ENDPOINT:-}
- OIDC_AUTH_USER_INFO_ENDPOINT=${OIDC_AUTH_USER_INFO_ENDPOINT:-}
- OIDC_AUTH_SCOPES=${OIDC_AUTH_SCOPES:-}
- OIDC_USER_INFO_MAPPING_USER_NAME=${OIDC_USER_INFO_MAPPING_USER_NAME:-}
- OIDC_USER_INFO_MAPPING_EMAIL=${OIDC_USER_INFO_MAPPING_EMAIL:-}
# 飞书云文档解析模式 export:导出为docx,blocks:根据飞书云文档块解析为markdown。默认为:export
- FEISHU_DOCX_PARSE_MODE=${FEISHU_DOCX_PARSE_MODE:-export}
depends_on:
redis:
condition: service_started
postgres:
condition: service_healthy
docreader:
condition: service_healthy
networks:
- WeKnora-network
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
# Sandbox 镜像:仅用于 build/pull,非常驻服务;app 执行 Skills 时按需 docker run 该镜像,用毕即释
sandbox:
image: wechatopenai/weknora-sandbox:${WEKNORA_VERSION:-latest}
container_name: WeKnora-sandbox
build:
context: .
dockerfile: docker/Dockerfile.sandbox
profiles:
- full
command: ["true"]
restart: "no"
docreader:
image: wechatopenai/weknora-docreader:${WEKNORA_VERSION:-latest}
build:
context: .
dockerfile: docker/Dockerfile.docreader
args:
- APT_MIRROR=${APT_MIRROR:-}
container_name: WeKnora-docreader
# docreader gRPC ships without auth/TLS and is only consumed by the app
# container over the WeKnora-network (DOCREADER_ADDR defaults to
# docreader:50051), so we don't publish 50051 to the host. `expose` keeps
# the port reachable inside the compose network for documentation only.
# If you need to call docreader from outside the host (e.g. for debugging),
# add a `ports:` entry locally, preferably bound to 127.0.0.1.
expose:
- "50051"
volumes:
- docreader-tmp:/tmp/docreader
environment:
# ========== 基础 ==========
- TZ=${TZ:-Asia/Shanghai}
- LOG_LEVEL=${LOG_LEVEL:-}
- DOCREADER_IMAGE_OUTPUT_DIR=/tmp/docreader
- MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-}
# docreader 对外 HTTP 抓取的 SSRF 白名单(逗号分隔;与 app 共用同名变量)
- SSRF_WHITELIST=${SSRF_WHITELIST:-}
- SSRF_WHITELIST_EXTRA=${SSRF_WHITELIST_EXTRA:-}
# ========== gRPC 服务端 TLS / 认证 ==========
- GRPC_TLS_ENABLED=${GRPC_TLS_ENABLED:-false}
- GRPC_TLS_CERT=${GRPC_TLS_CERT:-}
- GRPC_TLS_KEY=${GRPC_TLS_KEY:-}
- GRPC_TLS_CA=${GRPC_TLS_CA:-}
- GRPC_TLS_SERVER_NAME=${GRPC_TLS_SERVER_NAME:-}
# true 时强制要求客户端证书(mTLS)
- GRPC_MTLS_REQUIRE_CLIENT_CERT=${GRPC_MTLS_REQUIRE_CLIENT_CERT:-false}
- GRPC_AUTH_TOKEN=${GRPC_AUTH_TOKEN:-}
# ========== gRPC 服务参数 ==========
- DOCREADER_GRPC_MAX_WORKERS=${DOCREADER_GRPC_MAX_WORKERS:-4}
- DOCREADER_GRPC_MAX_FILE_SIZE_MB=${DOCREADER_GRPC_MAX_FILE_SIZE_MB:-${MAX_FILE_SIZE_MB:-50}}
- DOCREADER_GRPC_PORT=${DOCREADER_GRPC_PORT:-50051}
# ========== 解析通用 ==========
- DOCREADER_DOCX_MAX_PAGES=${DOCREADER_DOCX_MAX_PAGES:-0}
- DOCREADER_MARKITDOWN_MAX_WORKERS=${DOCREADER_MARKITDOWN_MAX_WORKERS:-1}
# 出站 HTTP/HTTPS 代理(抓取网页 / 远程图片时)
- DOCREADER_EXTERNAL_HTTP_PROXY=${DOCREADER_EXTERNAL_HTTP_PROXY:-}
- DOCREADER_EXTERNAL_HTTPS_PROXY=${DOCREADER_EXTERNAL_HTTPS_PROXY:-}
# ========== OpenDataLoader hybrid ==========
- DOCREADER_ODL_MAX_WORKERS=${DOCREADER_ODL_MAX_WORKERS:-1}
- DOCREADER_ODL_HYBRID=${DOCREADER_ODL_HYBRID:-off}
- DOCREADER_ODL_HYBRID_URL=${DOCREADER_ODL_HYBRID_URL:-http://odl-hybrid:5002}
- DOCREADER_ODL_HYBRID_MODE=${DOCREADER_ODL_HYBRID_MODE:-auto}
- DOCREADER_ODL_HYBRID_FALLBACK=${DOCREADER_ODL_HYBRID_FALLBACK:-false}
- DOCREADER_ODL_MARKDOWN_WITH_HTML=${DOCREADER_ODL_MARKDOWN_WITH_HTML:-false}
# ========== PDF 渲染 ==========
- DOCREADER_PDF_RENDER_MAX_WORKERS=${DOCREADER_PDF_RENDER_MAX_WORKERS:-1}
# 单个 PDF 内部扫描页渲染并行进程数(pdfium 非线程安全)。留空走代码默认 min(4,cpu),
# 避免在低核容器上过度并行(写死数值会覆盖 CPU 感知逻辑)。
- DOCREADER_PDF_RENDER_PARALLELISM=${DOCREADER_PDF_RENDER_PARALLELISM:-}
- DOCREADER_PDF_RENDER_DPI=${DOCREADER_PDF_RENDER_DPI:-200}
# JPEG 质量(代码默认 85;compose 此前误写 90,已对齐)
- DOCREADER_PDF_JPEG_QUALITY=${DOCREADER_PDF_JPEG_QUALITY:-85}
# 渲染页图最大长边像素(防超大页撑爆 gRPC 消息上限;密集中文建议 >=1600)
- DOCREADER_PDF_RENDER_MAX_EDGE=${DOCREADER_PDF_RENDER_MAX_EDGE:-2000}
# 强制 PDF 全页按扫描件解析(文本层差但可 OCR 时;速度/成本上升)
- DOCREADER_PDF_FORCE_SCANNED=${DOCREADER_PDF_FORCE_SCANNED:-false}
# ========== PDF 版式 / 文本调优 ==========
# glyph 间距超过 median 字宽的多少倍时插空格(默认 0.4)
- DOCREADER_PDF_WORD_GAP_WIDTH_RATIO=${DOCREADER_PDF_WORD_GAP_WIDTH_RATIO:-0.4}
# 丢弃窄边栏(arXiv 侧栏)的页宽比例阈值(默认 0.12)
- DOCREADER_PDF_MARGIN_COL_WIDTH_RATIO=${DOCREADER_PDF_MARGIN_COL_WIDTH_RATIO:-0.12}
- DOCREADER_PDF_MIN_HEADING_LINE_CHARS=${DOCREADER_PDF_MIN_HEADING_LINE_CHARS:-8}
# 清理 U+FFFE/软连字符、剥离矢量图轴文字、图表区按 JPEG 渲染
- DOCREADER_PDF_SANITIZE_TEXT=${DOCREADER_PDF_SANITIZE_TEXT:-true}
- DOCREADER_PDF_STRIP_CHART_DEBRIS=${DOCREADER_PDF_STRIP_CHART_DEBRIS:-true}
- DOCREADER_PDF_RENDER_VECTOR_FIGURES=${DOCREADER_PDF_RENDER_VECTOR_FIGURES:-true}
- DOCREADER_PDF_DETECT_HEADINGS=${DOCREADER_PDF_DETECT_HEADINGS:-}
- DOCREADER_PDF_LAYOUT_ORDERING=${DOCREADER_PDF_LAYOUT_ORDERING:-}
- DOCREADER_PDF_FILTER_HIDDEN_TEXT=${DOCREADER_PDF_FILTER_HIDDEN_TEXT:-}
# 扫描件判定:图片面积占比 >= 此值视为扫描页(默认留空走代码默认)
- DOCREADER_PDF_SCAN_IMAGE_RATIO=${DOCREADER_PDF_SCAN_IMAGE_RATIO:-}
- DOCREADER_PDF_SCAN_MIN_CHARS=${DOCREADER_PDF_SCAN_MIN_CHARS:-}
# 嵌入图片抽取
- DOCREADER_PDF_EXTRACT_EMBEDDED_IMAGES=${DOCREADER_PDF_EXTRACT_EMBEDDED_IMAGES:-}
- DOCREADER_PDF_EMBED_MIN_PIXELS=${DOCREADER_PDF_EMBED_MIN_PIXELS:-}
- DOCREADER_PDF_EMBED_MIN_AREA_RATIO=${DOCREADER_PDF_EMBED_MIN_AREA_RATIO:-}
- DOCREADER_PDF_EMBED_REPEAT_PAGE_FRAC=${DOCREADER_PDF_EMBED_REPEAT_PAGE_FRAC:-}
- DOCREADER_PDF_EMBED_MAX_IMAGES=${DOCREADER_PDF_EMBED_MAX_IMAGES:-}
# 图表区识别
- DOCREADER_PDF_MIN_CHART_REGION_AREA=${DOCREADER_PDF_MIN_CHART_REGION_AREA:-}
- DOCREADER_PDF_MIN_CHART_REGION_CHARS=${DOCREADER_PDF_MIN_CHART_REGION_CHARS:-}
- DOCREADER_PDF_MAX_CHART_REGION_AREA=${DOCREADER_PDF_MAX_CHART_REGION_AREA:-}
- DOCREADER_PDF_MAX_FIGURE_HEIGHT_RATIO=${DOCREADER_PDF_MAX_FIGURE_HEIGHT_RATIO:-}
healthcheck:
test: ["CMD", "grpc_health_probe", "-addr=localhost:50051"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- WeKnora-network
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
# OpenDataLoader hybrid backend (optional). Default --no-ocr (no EasyOCR/libGL).
# Local build only — not published to Docker Hub; use --profile odl-hybrid --build.
odl-hybrid:
build:
context: .
dockerfile: docker/Dockerfile.odl-hybrid
image: weknora-odl-hybrid:local
container_name: WeKnora-odl-hybrid
profiles:
- odl-hybrid
expose:
- "5002"
environment:
- ODL_HYBRID_EXTRA_ARGS=${ODL_HYBRID_EXTRA_ARGS:---no-ocr}
networks:
- WeKnora-network
restart: unless-stopped
# 修改的PostgreSQL配置
postgres:
image: paradedb/paradedb:v0.22.2-pg17
container_name: WeKnora-postgres
environment:
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_NAME}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- WeKnora-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s # 增加时间间隔
timeout: 10s # 增加超时时间
retries: 3 # 减少重试次数,让失败更快反馈
start_period: 30s # 给予初始启动更多时间
restart: unless-stopped
# 添加停机时的优雅退出时间
stop_grace_period: 1m
redis:
image: redis:7.0-alpine
container_name: WeKnora-redis
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
restart: always
networks:
- WeKnora-network
# One-shot init: copy the read-only template settings.yml into a named volume
# before searxng starts. Necessary because the searxng entrypoint sed-replaces
# `ultrasecretkey` in /etc/searxng/settings.yml in place; bind-mounting the
# repo file directly would write the resolved SEARXNG_SECRET back into the
# working tree (and prevent re-templating on subsequent restarts).
searxng-init:
image: busybox:1.36
container_name: WeKnora-searxng-init
command: ["sh", "-c", "cp /template/settings.yml /etc/searxng/settings.yml && chmod 0644 /etc/searxng/settings.yml"]
volumes:
- ./docker/searxng/settings.yml:/template/settings.yml:ro
- searxng_config:/etc/searxng
restart: "no"
networks:
- WeKnora-network
profiles:
- searxng
- full
searxng:
image: searxng/searxng:latest
container_name: WeKnora-searxng
# Bind to loopback by default; override SEARXNG_BIND=0.0.0.0 only after
# rotating SEARXNG_SECRET and (optionally) re-enabling limiter in settings.yml.
ports:
- "${SEARXNG_BIND:-127.0.0.1}:${SEARXNG_PORT:-8888}:8080"
volumes:
- searxng_config:/etc/searxng
environment:
- SEARXNG_BASE_URL=http://localhost:${SEARXNG_PORT:-8888}/
- INSTANCE_NAME=weknora-searxng
# Default value lets `docker compose --profile searxng up` work zero-config.
# Override via .env (e.g. `openssl rand -hex 32`) before exposing the
# instance beyond the default 127.0.0.1 bind, since secret_key signs
# image-proxy URLs and a shared default would be guessable by anyone.
- SEARXNG_SECRET=${SEARXNG_SECRET:-weknora-default-searxng-secret-rotate-before-exposing-publicly}
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
restart: unless-stopped
depends_on:
searxng-init:
condition: service_completed_successfully
networks:
- WeKnora-network
profiles:
- searxng
- full
minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
container_name: WeKnora-minio
ports:
- "${MINIO_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
environment:
- MINIO_ROOT_USER=${MINIO_ACCESS_KEY_ID:-minioadmin}
- MINIO_ROOT_PASSWORD=${MINIO_SECRET_ACCESS_KEY:-minioadmin}
command: server --console-address ":9001" /data
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
networks:
- WeKnora-network
profiles:
- minio
- full
neo4j:
image: neo4j:2025.10.1
container_name: WeKnora-neo4j
volumes:
- neo4j-data:/data
environment:
- NEO4J_AUTH=${NEO4J_USERNAME:-neo4j}/${NEO4J_PASSWORD:-password}
- NEO4J_apoc_export_file_enabled=true
- NEO4J_apoc_import_file_enabled=true
- NEO4J_apoc_import_file_use__neo4j__config=true
- NEO4JLABS_PLUGINS=["apoc"]
ports:
- "7474:7474"
- "7687:7687"
restart: always
networks:
- WeKnora-network
profiles:
- neo4j
- full
qdrant:
image: qdrant/qdrant:v1.16.2
container_name: WeKnora-qdrant
ports:
- "${QDRANT_REST_PORT:-6333}:6333"
- "${QDRANT_PORT:-6334}:6334"
volumes:
- qdrant_data:/qdrant/storage
networks:
- WeKnora-network
restart: unless-stopped
profiles:
- qdrant
- full
milvus:
image: milvusdb/milvus:v2.6.11
container_name: WeKnora-milvus
security_opt:
- seccomp:unconfined
command: ["milvus", "run", "standalone"]
environment:
- ETCD_USE_EMBED=true
- ETCD_DATA_DIR=/var/lib/milvus/etcd
- COMMON_STORAGETYPE=local
- DEPLOY_MODE=STANDALONE
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
interval: 30s
start_period: 90s
timeout: 20s
retries: 3
ports:
- "19530:19530"
- "9091:9091"
volumes:
- milvus_data:/var/lib/milvus
networks:
- WeKnora-network
restart: unless-stopped
profiles:
- milvus
weaviate:
image: semitechnologies/weaviate:1.28.4
container_name: WeKnora-weaviate
environment:
- PERSISTENCE_DATA_PATH=/var/lib/weaviate
- CLUSTER_HOSTNAME=node1
- DEFAULT_VECTORIZER_MODULE=none
- ENABLE_MODULES=none
- AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true
- CLUSTER_GOSSIP_BIND_PORT=7000
- CLUSTER_DATA_BIND_PORT=7001
- RAFT_BOOTSTRAP_EXPECT=1
ports:
- "9035:8080"
- "50052:50051"
volumes:
- weaviate_data:/var/lib/weaviate
networks:
- WeKnora-network
restart: unless-stopped
profiles:
- weaviate
# ---------------------------------------------------------------------------
# Apache Doris 4.1FE + BE 单实例 standalone 部署,opt-in via `--profile doris`
#
# 版本要求:必须 >= 3.0HNSW ANN 索引、cosine_distance_approximate、
# Stream Load partial_columns)。本 compose 使用 4.1 官方稳定 tag。
#
# 端口说明:
# - 9030 (FE MySQL)WeKnora 主链路读写、SQL 查询都走此端口。
# - 8030 (FE HTTP)Stream Load partial update 入口。
# - 8040 (BE) BE 心跳与数据传输;FE 会把 stream load 重定向到 BE。
#
# 启用方式:
# docker compose --profile doris up -d
#
# 数据持久化在 doris_fe_data / doris_be_data 卷中。
# ---------------------------------------------------------------------------
doris-fe:
image: apache/doris:fe-4.1.0
container_name: WeKnora-doris-fe
hostname: doris-fe
environment:
- FE_SERVERS=fe1:doris-fe:9010
- FE_ID=1
ports:
- "${DORIS_FE_HTTP_PORT:-8030}:8030"
- "${DORIS_FE_MYSQL_PORT:-9030}:9030"
volumes:
- doris_fe_meta:/opt/apache-doris/fe/doris-meta
- doris_fe_log:/opt/apache-doris/fe/log
networks:
- WeKnora-network
restart: unless-stopped
profiles:
- doris
doris-be:
image: apache/doris:be-4.1.0
container_name: WeKnora-doris-be
hostname: doris-be
environment:
- FE_SERVERS=fe1:doris-fe:9010
- BE_ADDR=doris-be:9050
depends_on:
- doris-fe
ports:
- "${DORIS_BE_HTTP_PORT:-8040}:8040"
volumes:
- doris_be_storage:/opt/apache-doris/be/storage
- doris_be_log:/opt/apache-doris/be/log
networks:
- WeKnora-network
restart: unless-stopped
profiles:
- doris
dex:
image: dexidp/dex:latest
container_name: dex
ports:
- "5556:5556"
volumes:
- ./misc/dex-config.yaml:/etc/dex/config.yaml
command: ["dex", "serve", "/etc/dex/config.yaml"]
profiles:
- dex
- full
# ---------------------------------------------------------------------------
# Langfuse 自建可观测栈 (opt-in via `--profile langfuse`)
#
# 为了最小化资源占用,Langfuse 会复用 WeKnora 已有的 postgres 和 redis
# - postgres:在 WeKnora-postgres 中创建独立的 "langfuse" 数据库
# - redis :复用 WeKnora-redis 的 DB 1WeKnora 自己用 DB 0
# 需要新增的只有 Langfuse 应用自身 + ClickHouseOLAP+ 专用 MinIO(事件/媒体 S3)。
#
# 启用方式:
# docker compose --profile langfuse up -d
# 启用后:
# 1. Langfuse UI: http://localhost:3000 (首次访问注册管理员并在 Settings → API Keys 生成 key
# 2. 在 .env 里把 LANGFUSE_HOST 改成 http://langfuse-web:3000(容器间通信)
# 并填入刚生成的 LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY
# 3. 重启 app 容器:docker compose up -d app
# 相关默认密码已写成占位符,生产部署务必用 openssl rand 重新生成!
# ---------------------------------------------------------------------------
# 一次性初始化脚本:在已有的 WeKnora-postgres 里创建 langfuse 数据库(幂等,数据库已存在则跳过)
# 复用 WeKnora-postgres 的 ParadeDB 镜像,避免多拉一个 postgres 镜像
langfuse-db-init:
image: paradedb/paradedb:v0.22.2-pg17
container_name: WeKnora-langfuse-db-init
depends_on:
postgres:
condition: service_healthy
environment:
PGPASSWORD: ${DB_PASSWORD}
# ${LANGFUSE_DB_NAME:-langfuse} / ${DB_USER} 由 compose 解析成字面量后再传给 shell;
# 脚本中需要 shell 自己展开的变量(无)一律用 $$ 转义。
entrypoint: ["sh", "-c"]
command:
- |
set -e
echo "[langfuse-db-init] ensuring database '${LANGFUSE_DB_NAME:-langfuse}' exists in WeKnora-postgres..."
# 先刷新现有库的 collation(镜像 ICU 2.36 与宿主 2.41 不匹配时必须做),否则 CREATE DATABASE 会失败
psql -h postgres -U ${DB_USER} -d postgres -v ON_ERROR_STOP=0 -c "ALTER DATABASE template1 REFRESH COLLATION VERSION;" >/dev/null 2>&1 || true
psql -h postgres -U ${DB_USER} -d postgres -v ON_ERROR_STOP=0 -c "ALTER DATABASE postgres REFRESH COLLATION VERSION;" >/dev/null 2>&1 || true
# 幂等创建:已存在则跳过;不存在则从 template0 克隆(template0 永远不会有 collation 漂移)
if psql -h postgres -U ${DB_USER} -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='${LANGFUSE_DB_NAME:-langfuse}'" | grep -q 1; then
echo "[langfuse-db-init] database '${LANGFUSE_DB_NAME:-langfuse}' already exists, skipping."
else
psql -h postgres -U ${DB_USER} -d postgres -v ON_ERROR_STOP=1 -c "CREATE DATABASE \"${LANGFUSE_DB_NAME:-langfuse}\" TEMPLATE template0;"
echo "[langfuse-db-init] database '${LANGFUSE_DB_NAME:-langfuse}' created."
fi
echo "[langfuse-db-init] done."
networks:
- WeKnora-network
restart: "no"
profiles:
- langfuse
- full
langfuse-clickhouse:
image: clickhouse/clickhouse-server:24.8
container_name: WeKnora-langfuse-clickhouse
restart: unless-stopped
user: "101:101"
environment:
CLICKHOUSE_DB: default
CLICKHOUSE_USER: ${LANGFUSE_CLICKHOUSE_USER:-clickhouse}
CLICKHOUSE_PASSWORD: ${LANGFUSE_CLICKHOUSE_PASSWORD:-clickhouse}
volumes:
- langfuse_clickhouse_data:/var/lib/clickhouse
- langfuse_clickhouse_logs:/var/log/clickhouse-server
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1
interval: 5s
timeout: 5s
retries: 10
start_period: 10s
networks:
- WeKnora-network
profiles:
- langfuse
- full
langfuse-minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
container_name: WeKnora-langfuse-minio
restart: unless-stopped
entrypoint: sh
# 启动前创建 `langfuse` 桶,Langfuse 开箱即用
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
environment:
MINIO_ROOT_USER: ${LANGFUSE_MINIO_USER:-langfuseminio}
MINIO_ROOT_PASSWORD: ${LANGFUSE_MINIO_PASSWORD:-langfuseminiosecret}
ports:
# 9100:S3 API(浏览器上传媒体时会直连此端口,必须对外暴露)
- "${LANGFUSE_MINIO_S3_PORT:-9100}:9000"
# 9101:MinIO 控制台(可选,方便排障)
- "${LANGFUSE_MINIO_CONSOLE_PORT:-9101}:9001"
volumes:
- langfuse_minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 10s
retries: 5
networks:
- WeKnora-network
profiles:
- langfuse
- full
langfuse-worker:
image: langfuse/langfuse-worker:3
container_name: WeKnora-langfuse-worker
restart: unless-stopped
depends_on: &langfuse-depends-on
# 复用 WeKnora 已有的 postgres/redis,需要先完成 langfuse 数据库的创建
langfuse-db-init:
condition: service_completed_successfully
redis:
condition: service_started
langfuse-clickhouse:
condition: service_healthy
langfuse-minio:
condition: service_healthy
# 用 wrapper entrypoint:在容器启动时把 DB_PASSWORD / REDIS_PASSWORD 做 URL 编码,
# 避免 DB_PASSWORD 含 '@' / '#' 等字符导致 Prisma 无法解析 DATABASE_URL (P1013)。
# Langfuse 镜像基于 Node.js,直接用 node 的 encodeURIComponent,不引外部依赖。
# 注意:compose 覆盖 entrypoint 会清空镜像默认 CMD,因此在 wrapper 末尾写死 exec 的原始命令。
entrypoint:
- /bin/sh
- -ec
- |
_enc() { node -e 'process.stdout.write(encodeURIComponent(process.argv[1]))' "$$1"; }
DU=$$(_enc "$$_LF_DB_USER")
DP=$$(_enc "$$_LF_DB_PASSWORD")
RP=$$(_enc "$$_LF_REDIS_PASSWORD")
export DATABASE_URL="postgresql://$$DU:$$DP@postgres:5432/$$_LF_DB_NAME"
export REDIS_CONNECTION_STRING="redis://:$$RP@redis:6379/$$_LF_REDIS_DB"
unset _LF_DB_USER _LF_DB_PASSWORD _LF_REDIS_PASSWORD
exec dumb-init -- ./worker/entrypoint.sh node worker/dist/index.js
environment: &langfuse-env
# 原始凭证(未 URL 编码),由 entrypoint wrapper 读取并组装成 DATABASE_URL / REDIS_CONNECTION_STRING
_LF_DB_USER: ${DB_USER}
_LF_DB_PASSWORD: ${DB_PASSWORD}
_LF_DB_NAME: ${LANGFUSE_DB_NAME:-langfuse}
_LF_REDIS_PASSWORD: ${REDIS_PASSWORD}
_LF_REDIS_DB: ${LANGFUSE_REDIS_DB:-1}
# SALT / ENCRYPTION_KEY 生产环境务必重新生成:
# SALT: openssl rand -base64 32
# ENCRYPTION_KEY: openssl rand -hex 32
SALT: ${LANGFUSE_SALT:-weknora-langfuse-dev-salt-change-me}
ENCRYPTION_KEY: ${LANGFUSE_ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000}
NEXTAUTH_URL: ${LANGFUSE_NEXTAUTH_URL:-http://localhost:3000}
NEXTAUTH_SECRET: ${LANGFUSE_NEXTAUTH_SECRET:-weknora-langfuse-dev-nextauth-secret-change-me}
TELEMETRY_ENABLED: ${LANGFUSE_TELEMETRY_ENABLED:-false}
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: "false"
CLICKHOUSE_URL: http://langfuse-clickhouse:8123
CLICKHOUSE_MIGRATION_URL: clickhouse://langfuse-clickhouse:9000
CLICKHOUSE_USER: ${LANGFUSE_CLICKHOUSE_USER:-clickhouse}
CLICKHOUSE_PASSWORD: ${LANGFUSE_CLICKHOUSE_PASSWORD:-clickhouse}
CLICKHOUSE_CLUSTER_ENABLED: "false"
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: langfuse
LANGFUSE_S3_EVENT_UPLOAD_REGION: auto
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_MINIO_USER:-langfuseminio}
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_MINIO_PASSWORD:-langfuseminiosecret}
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: http://langfuse-minio:9000
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: "true"
LANGFUSE_S3_EVENT_UPLOAD_PREFIX: events/
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: langfuse
LANGFUSE_S3_MEDIA_UPLOAD_REGION: auto
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_MINIO_USER:-langfuseminio}
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_MINIO_PASSWORD:-langfuseminiosecret}
# 媒体上传用的外部 endpoint(浏览器直连),默认指向宿主机映射的 9100 端口
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT:-http://localhost:9100}
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: "true"
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: media/
networks:
- WeKnora-network
profiles:
- langfuse
- full
langfuse-web:
image: langfuse/langfuse:3
container_name: WeKnora-langfuse-web
restart: unless-stopped
depends_on: *langfuse-depends-on
ports:
- "${LANGFUSE_WEB_PORT:-3000}:3000"
# 同样走 wrapper entrypoint,末尾写死 web 的原始启动命令(见镜像 Dockerfile 的 CMD
entrypoint:
- /bin/sh
- -ec
- |
_enc() { node -e 'process.stdout.write(encodeURIComponent(process.argv[1]))' "$$1"; }
DU=$$(_enc "$$_LF_DB_USER")
DP=$$(_enc "$$_LF_DB_PASSWORD")
RP=$$(_enc "$$_LF_REDIS_PASSWORD")
export DATABASE_URL="postgresql://$$DU:$$DP@postgres:5432/$$_LF_DB_NAME"
export REDIS_CONNECTION_STRING="redis://:$$RP@redis:6379/$$_LF_REDIS_DB"
unset _LF_DB_USER _LF_DB_PASSWORD _LF_REDIS_PASSWORD
if [ -n "$$NEXT_PUBLIC_LANGFUSE_CLOUD_REGION" ]; then
exec dumb-init -- ./web/entrypoint.sh node --import dd-trace/initialize.mjs ./web/server.js --keepAliveTimeout 110000
else
exec dumb-init -- ./web/entrypoint.sh node ./web/server.js --keepAliveTimeout 110000
fi
environment:
<<: *langfuse-env
# 可选:首次启动时自动创建组织、项目和管理员(填了就不用在 UI 上手动注册)
LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-}
LANGFUSE_INIT_ORG_NAME: ${LANGFUSE_INIT_ORG_NAME:-}
LANGFUSE_INIT_PROJECT_ID: ${LANGFUSE_INIT_PROJECT_ID:-}
LANGFUSE_INIT_PROJECT_NAME: ${LANGFUSE_INIT_PROJECT_NAME:-}
LANGFUSE_INIT_PROJECT_PUBLIC_KEY: ${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
LANGFUSE_INIT_PROJECT_SECRET_KEY: ${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
LANGFUSE_INIT_USER_EMAIL: ${LANGFUSE_INIT_USER_EMAIL:-}
LANGFUSE_INIT_USER_NAME: ${LANGFUSE_INIT_USER_NAME:-}
LANGFUSE_INIT_USER_PASSWORD: ${LANGFUSE_INIT_USER_PASSWORD:-}
networks:
- WeKnora-network
profiles:
- langfuse
- full
mcp:
build:
context: ./mcp-server
container_name: WeKnora-mcp
ports:
- "${MCP_PORT:-8082}:8000"
environment:
- WEKNORA_BASE_URL=http://app:8080/api/v1
- WEKNORA_API_KEY=${WEKNORA_API_KEY:-}
# HTTP/SSE 传输必填:mcp-server 启动时若缺少此项会拒绝启动(客户端以 Authorization: Bearer 携带)
- MCP_SERVER_AUTH_TOKEN=${MCP_SERVER_AUTH_TOKEN:-}
# 调用 WeKnora REST 的读超时(秒,默认 300)
- WEKNORA_CHAT_TIMEOUT=${WEKNORA_CHAT_TIMEOUT:-300}
# 是否校验 WeKnora 后端 TLS 证书(默认 true;自签证书可设 false)
- WEKNORA_VERIFY_SSL=${WEKNORA_VERIFY_SSL:-true}
# 允许上传的目录白名单(逗号分隔;留空禁用文件上传工具)
- MCP_ALLOWED_UPLOAD_DIRS=${MCP_ALLOWED_UPLOAD_DIRS:-}
depends_on:
app:
condition: service_healthy
networks:
- WeKnora-network
restart: unless-stopped
profiles:
- full
networks:
WeKnora-network:
driver: bridge
volumes:
postgres-data:
data-files:
docreader-tmp:
minio_data:
neo4j-data:
qdrant_data:
milvus_data:
weaviate_data:
doris_fe_meta:
doris_fe_log:
doris_be_storage:
doris_be_log:
langfuse_clickhouse_data:
langfuse_clickhouse_logs:
langfuse_minio_data:
searxng_config: