From 6a03aed809991f93fb8a0b3a3c34b7f83dd2906f Mon Sep 17 00:00:00 2001 From: yuchengen Date: Fri, 29 May 2026 10:52:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=8C=E4=BC=98=E5=8C=96=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E3=80=82=E5=A2=9E=E5=8A=A0=E4=B8=80=E9=94=AE?= =?UTF-8?q?=E9=83=A8=E7=BD=B2compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frameworks/Dify/1.14.1/Dockerfile | 62 ++++ frameworks/Dify/1.14.1/README.md | 318 ++++++++++++++++++++ frameworks/Dify/1.14.1/build.conf | 4 + frameworks/Dify/1.14.1/compose-template.yml | 195 ++++++++++++ frameworks/Dify/1.14.1/dify-entrypoint.sh | 73 +++++ frameworks/Dify/1.14.1/test.sh | 195 ++++++++++++ frameworks/Dify/1.14.1/test_result.png | Bin 0 -> 27940 bytes 7 files changed, 847 insertions(+) create mode 100644 frameworks/Dify/1.14.1/Dockerfile create mode 100644 frameworks/Dify/1.14.1/README.md create mode 100644 frameworks/Dify/1.14.1/build.conf create mode 100644 frameworks/Dify/1.14.1/compose-template.yml create mode 100644 frameworks/Dify/1.14.1/dify-entrypoint.sh create mode 100644 frameworks/Dify/1.14.1/test.sh create mode 100644 frameworks/Dify/1.14.1/test_result.png diff --git a/frameworks/Dify/1.14.1/Dockerfile b/frameworks/Dify/1.14.1/Dockerfile new file mode 100644 index 0000000..8dd17dd --- /dev/null +++ b/frameworks/Dify/1.14.1/Dockerfile @@ -0,0 +1,62 @@ +# syntax=docker/dockerfile:1.7 + +ARG DIFY_VERSION=1.14.1 +ARG PLUGIN_DAEMON_VERSION=0.6.0-local + +FROM langgenius/dify-api:${DIFY_VERSION} AS dify_api +FROM langgenius/dify-web:${DIFY_VERSION} AS dify_web +FROM langgenius/dify-plugin-daemon:${PLUGIN_DAEMON_VERSION} AS plugin_daemon +FROM node:22-bookworm-slim AS node_runtime + +FROM opencloudos/opencloudos9-cuda-devel:12.8 +LABEL maintainer="stronking 363133710@qq.com" +LABEL org.opencontainers.image.source="https://gitee.com/OpenCloudOS/ai-agent-container" +LABEL org.opencontainers.image.description="Dify 1.14.1 on OpenCloudOS 9 with Python 3.11, CUDA 12.8 ,Nodejs 22" +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +ENV LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + PYTHONIOENCODING=utf-8 \ + TZ=Asia/Shanghai \ + EDITION=SELF_HOSTED \ + DEPLOY_ENV=PRODUCTION \ + FLASK_APP=app.py \ + PATH=/app/api/.venv/bin:/usr/local/bin:/usr/bin:/bin \ + NODE_ENV=production \ + PORT=3000 \ + NLTK_DATA=/usr/local/share/nltk_data \ + TIKTOKEN_CACHE_DIR=/app/api/.tiktoken_cache + +# 完整复制官方 Dify API Python Runtime,包含 Python 3.12、pip、site-packages 等 +COPY --from=dify_api /usr/local /usr/local + +# 再复制 Node.js,避免 /usr/local/bin 被 Python Runtime 覆盖 +COPY --from=node_runtime /usr/local/bin/node /usr/local/bin/node +COPY --from=node_runtime /usr/local/bin/npm /usr/local/bin/npm +COPY --from=node_runtime /usr/local/bin/npx /usr/local/bin/npx +COPY --from=node_runtime /usr/local/lib/node_modules /usr/local/lib/node_modules + +RUN mkdir -p \ + /app/api \ + /app/web-root \ + /app/plugin-daemon-root \ + /app/storage \ + /usr/local/share/nltk_data + +COPY --from=dify_api /app/api /app/api +COPY --from=dify_api /usr/local/share/nltk_data /usr/local/share/nltk_data +COPY --from=dify_api /entrypoint.sh /app/api-entrypoint.sh + +COPY --from=dify_web /app /app/web-root + +COPY --from=plugin_daemon /app /app/plugin-daemon-root + +COPY dify-entrypoint.sh /usr/local/bin/dify-entrypoint + + +RUN sed -i 's/\r$//' /usr/local/bin/dify-entrypoint /app/api-entrypoint.sh \ + && chmod +x /usr/local/bin/dify-entrypoint /app/api-entrypoint.sh + +EXPOSE 5001 3000 5002 5003 + +CMD ["/usr/local/bin/dify-entrypoint"] \ No newline at end of file diff --git a/frameworks/Dify/1.14.1/README.md b/frameworks/Dify/1.14.1/README.md new file mode 100644 index 0000000..23686fd --- /dev/null +++ b/frameworks/Dify/1.14.1/README.md @@ -0,0 +1,318 @@ +# Dify on OpenCloudOS 9 + +## 基本信息 + +* **框架版本**:Dify v1.14.1 +* **基础镜像**:`opencloudos/opencloudos9-cuda-devel:12.8` +* **Python 版本**:3.11 +* **CUDA 版本**:12.8 +* **Node.js 版本**:22 +* **Plugin Daemon 版本**:`0.6.0-local` +* **镜像模式**:单镜像,多角色容器运行 + +该镜像合并了以下 Dify 组件: + +```text +api +worker +beat +web +plugin_daemon +``` + +不包含以下外部依赖: + +```text +PostgreSQL +Redis +Nginx +向量数据库 +sandbox +ssrf_proxy +``` + +## 构建 + +```bash +docker build -t oc9-dify:1.14.1 . +``` + +也可以显式指定版本: + +```bash +docker build \ + --build-arg DIFY_VERSION=1.14.1 \ + --build-arg PLUGIN_DAEMON_VERSION=0.6.0-local \ + -t oc9-dify:1.14.1 . +``` + +## 使用示例 + +查看 Python 版本: + +```bash +docker run --rm oc9-dify:1.14.1 \ + python --version +``` + +查看 Dify 后端虚拟环境 Python: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.14.1 \ + -c "/app/api/.venv/bin/python --version" +``` + +查看 Node.js 版本: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.14.1 \ + -c "node --version" +``` + +查看角色启动入口: + +```bash +docker run --rm oc9-dify:1.14.1 +``` + +默认启动: + +```text +ROLE=api +``` + +## 运行方式 + +该镜像通过 `ROLE` 环境变量区分启动角色。 + +### 启动 API + +```bash +docker run -d \ + --name dify-api \ + -e ROLE=api \ + -e DB_HOST= \ + -e DB_PORT=5432 \ + -e DB_USERNAME=dify \ + -e DB_PASSWORD= \ + -e DB_DATABASE=dify \ + -e REDIS_HOST= \ + -e REDIS_PORT=6379 \ + -e REDIS_PASSWORD= \ + -p 5001:5001 \ + oc9-dify:1.14.1 +``` + +### 启动 Worker + +```bash +docker run -d \ + --name dify-worker \ + -e ROLE=worker \ + -e DB_HOST= \ + -e DB_PORT=5432 \ + -e DB_USERNAME=dify \ + -e DB_PASSWORD= \ + -e DB_DATABASE=dify \ + -e REDIS_HOST= \ + -e REDIS_PORT=6379 \ + -e REDIS_PASSWORD= \ + oc9-dify:1.14.1 +``` + +### 启动 Beat + +```bash +docker run -d \ + --name dify-beat \ + -e ROLE=beat \ + -e DB_HOST= \ + -e DB_PORT=5432 \ + -e DB_USERNAME=dify \ + -e DB_PASSWORD= \ + -e DB_DATABASE=dify \ + -e REDIS_HOST= \ + -e REDIS_PORT=6379 \ + -e REDIS_PASSWORD= \ + oc9-dify:1.14.1 +``` + +### 启动 Web + +```bash +docker run -d \ + --name dify-web \ + -e ROLE=web \ + -e CONSOLE_API_URL=http://:5001 \ + -e APP_API_URL=http://:5001 \ + -p 3000:3000 \ + oc9-dify:1.14.1 +``` + +### 启动 Plugin Daemon + +```bash +docker run -d \ + --name dify-plugin-daemon \ + -e ROLE=plugin_daemon \ + -e DB_HOST= \ + -e DB_PORT=5432 \ + -e DB_USERNAME=dify \ + -e DB_PASSWORD= \ + -e DB_DATABASE=dify_plugin \ + -e DB_SSL_MODE=disable \ + -e REDIS_HOST= \ + -e REDIS_PORT=6379 \ + -e REDIS_PASSWORD= \ + -e SERVER_PORT=5002 \ + -e SERVER_KEY= \ + -e DIFY_INNER_API_URL=http://:5001 \ + -e DIFY_INNER_API_KEY= \ + -e PLUGIN_WORKING_PATH=/app/storage/cwd \ + -e PLUGIN_STORAGE_LOCAL_ROOT=/app/storage \ + -v ./volumes/plugin_daemon:/app/storage \ + -p 5002:5002 \ + -p 5003:5003 \ + oc9-dify:1.14.1 +``` + +## docker-compose 示例 + +```yaml +services: + api: + image: oc9-dify:1.14.1 + environment: + ROLE: api + DB_HOST: postgres + DB_PORT: 5432 + DB_USERNAME: dify + DB_PASSWORD: dify + DB_DATABASE: dify + REDIS_HOST: redis + REDIS_PORT: 6379 + REDIS_PASSWORD: redis + PLUGIN_DAEMON_URL: http://plugin_daemon:5002 + INNER_API_KEY_FOR_PLUGIN: change-this-key + ports: + - "5001:5001" + volumes: + - ./volumes/app/storage:/app/api/storage + + worker: + image: oc9-dify:1.14.1 + environment: + ROLE: worker + DB_HOST: postgres + DB_PORT: 5432 + DB_USERNAME: dify + DB_PASSWORD: dify + DB_DATABASE: dify + REDIS_HOST: redis + REDIS_PORT: 6379 + REDIS_PASSWORD: redis + INNER_API_KEY_FOR_PLUGIN: change-this-key + volumes: + - ./volumes/app/storage:/app/api/storage + + beat: + image: oc9-dify:1.14.1 + environment: + ROLE: beat + DB_HOST: postgres + DB_PORT: 5432 + DB_USERNAME: dify + DB_PASSWORD: dify + DB_DATABASE: dify + REDIS_HOST: redis + REDIS_PORT: 6379 + REDIS_PASSWORD: redis + + web: + image: oc9-dify:1.14.1 + environment: + ROLE: web + CONSOLE_API_URL: http://localhost:5001 + APP_API_URL: http://localhost:5001 + ports: + - "3000:3000" + + plugin_daemon: + image: oc9-dify:1.14.1 + environment: + ROLE: plugin_daemon + DB_HOST: postgres + DB_PORT: 5432 + DB_USERNAME: dify + DB_PASSWORD: dify + DB_DATABASE: dify_plugin + DB_SSL_MODE: disable + REDIS_HOST: redis + REDIS_PORT: 6379 + REDIS_PASSWORD: redis + SERVER_PORT: 5002 + SERVER_KEY: change-this-plugin-key + DIFY_INNER_API_URL: http://api:5001 + DIFY_INNER_API_KEY: change-this-key + PLUGIN_WORKING_PATH: /app/storage/cwd + PLUGIN_STORAGE_LOCAL_ROOT: /app/storage + volumes: + - ./volumes/plugin_daemon:/app/storage + ports: + - "5002:5002" + - "5003:5003" +``` + +## 已知问题 + +* 该镜像不包含 PostgreSQL、Redis、向量数据库、sandbox、ssrf_proxy,需要外部提供。 +* `api`、`worker`、`beat`、`web`、`plugin_daemon` 必须使用同一个 Dify 版本构建。 +* `plugin_daemon` 版本必须与官方 compose 对齐,Dify `1.14.1` 对应 `0.6.0-local`。 +* 插件目录 `/app/storage` 必须持久化,否则插件安装状态可能丢失。 +* Web 服务需要正确配置 `CONSOLE_API_URL` 和 `APP_API_URL`。 +* Dify 后端默认不需要 CUDA,也不依赖 torch;CUDA 主要用于你额外部署本地模型、embedding 或 rerank 服务。 +* 生产环境建议仍使用外部 Nginx 或网关统一暴露 Web/API。 + + +### Dify镜像版本与plugin_daemon版本对应 +| Dify 版本 | plugin-daemon 版本 | +| ------- | ---------------- | +| 1.11.3 | `0.5.2-local` | +| 1.11.4 | `0.5.2-local` | +| 1.12.0 | `0.5.3-local` | +| 1.12.1 | `0.5.3-local` | +| 1.13.0 | `0.5.3-local` | +| 1.13.1 | `0.5.4-local` | +| 1.13.2 | `0.5.4-local` | +| 1.13.3 | `0.5.3-local` | +| 1.14.1 | `0.6.0-local` | +| 1.14.2 | `0.6.1-local` | + + +### 版本之间真正的区别(部署角度) +| 版本 | 特征 | 是否推荐生产 | +| ------ | ----------------- | ------ | +| 1.11.3 | 插件体系稳定化开始 | 可 | +| 1.11.4 | 安全修复版 | 推荐 | +| 1.12.0 | Summary Index 大升级 | 谨慎 | +| 1.12.1 | 修复 1.12 问题 | 推荐 | +| 1.13.0 | Agent 架构升级 | 可 | +| 1.13.1 | Worker/plugin 修复 | 推荐 | +| 1.13.2 | 稳定性修复 | 推荐 | +| 1.13.3 | 1.13 最稳定版 | 强烈推荐 | +| 1.14.1 | 企业化增强 | 推荐 | +| 1.14.2 | 当前最稳定 | 最推荐 | + +## 数据库兼容性(很重要) + +1.11 ——> 1.12 风险很大 +~~~ +知识库 schema +索引 schema +~~~ +变化明显 + + + +# 部署配置获取 +* OpenCloudOS: https://gitee.com/OpenCloudOS/ai-agent-container/tree/master/frameworks \ No newline at end of file diff --git a/frameworks/Dify/1.14.1/build.conf b/frameworks/Dify/1.14.1/build.conf new file mode 100644 index 0000000..ddbc0e5 --- /dev/null +++ b/frameworks/Dify/1.14.1/build.conf @@ -0,0 +1,4 @@ +# Dify 1.14.1 [Api,Web,Plugin]on OpenCloudOS 9 (GPU) +IMAGE_NAME=oc9-dify +IMAGE_TAG=1.14.1 +GPU_TEST=false \ No newline at end of file diff --git a/frameworks/Dify/1.14.1/compose-template.yml b/frameworks/Dify/1.14.1/compose-template.yml new file mode 100644 index 0000000..5e53027 --- /dev/null +++ b/frameworks/Dify/1.14.1/compose-template.yml @@ -0,0 +1,195 @@ +name: dify-opencloudos + +x-dify-image: &dify_image oc9-dify:1.14.1 + +x-common-env: &common_env + EDITION: SELF_HOSTED + DEPLOY_ENV: PRODUCTION + CONSOLE_API_URL: http://localhost:5001 + CONSOLE_WEB_URL: http://localhost:3000 + SERVICE_API_URL: http://localhost:5001 + APP_API_URL: http://localhost:5001 + APP_WEB_URL: http://localhost:3000 + SECRET_KEY: "change-this-to-a-long-random-secret" + + DB_HOST: postgres + DB_PORT: 5432 + DB_USERNAME: postgres + DB_PASSWORD: dify_postgres_password + DB_DATABASE: dify + + REDIS_HOST: redis + REDIS_PORT: 6379 + REDIS_PASSWORD: dify_redis_password + CELERY_BROKER_URL: redis://:dify_redis_password@redis:6379/1 + + STORAGE_TYPE: local + STORAGE_LOCAL_PATH: /app/storage + + VECTOR_STORE: weaviate + WEAVIATE_ENDPOINT: http://weaviate:8080 + WEAVIATE_API_KEY: "" + + PLUGIN_DAEMON_URL: http://plugin_daemon:5002 + PLUGIN_DAEMON_KEY: "change-this-plugin-key" + PLUGIN_MAX_PACKAGE_SIZE: 52428800 + PLUGIN_PPROF_ENABLED: "false" + +services: + api: + image: *dify_image + container_name: dify-api + restart: unless-stopped + environment: + <<: *common_env + ROLE: api + MODE: api + ports: + - "5001:5001" + volumes: + - dify_storage:/app/storage + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + weaviate: + condition: service_started + plugin_daemon: + condition: service_started + networks: + - dify + + worker: + image: *dify_image + container_name: dify-worker + restart: unless-stopped + environment: + <<: *common_env + ROLE: worker + MODE: worker + volumes: + - dify_storage:/app/storage + depends_on: + - api + - redis + - postgres + networks: + - dify + + beat: + image: *dify_image + container_name: dify-beat + restart: unless-stopped + environment: + <<: *common_env + ROLE: beat + MODE: beat + volumes: + - dify_storage:/app/storage + depends_on: + - api + - redis + - postgres + networks: + - dify + + web: + image: *dify_image + container_name: dify-web + restart: unless-stopped + environment: + <<: *common_env + ROLE: web + PORT: 3000 + NEXT_PUBLIC_API_PREFIX: http://localhost:5001/console/api + NEXT_PUBLIC_PUBLIC_API_PREFIX: http://localhost:5001/api + ports: + - "3000:3000" + depends_on: + - api + networks: + - dify + + plugin_daemon: + image: *dify_image + container_name: dify-plugin-daemon + restart: unless-stopped + environment: + <<: *common_env + ROLE: plugin_daemon + SERVER_PORT: 5002 + PLUGIN_WORKING_PATH: /app/storage/cwd + PLUGIN_STORAGE_LOCAL_ROOT: /app/storage + DIFY_INNER_API_URL: http://api:5001 + DIFY_INNER_API_KEY: "change-this-plugin-key" + ports: + - "5002:5002" + - "5003:5003" + volumes: + - dify_storage:/app/storage + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - dify + + postgres: + image: postgres:15-alpine + container_name: dify-postgres + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: dify_postgres_password + POSTGRES_DB: dify + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d dify"] + interval: 5s + timeout: 5s + retries: 30 + networks: + - dify + + redis: + image: redis:6-alpine + container_name: dify-redis + restart: unless-stopped + command: redis-server --requirepass dify_redis_password + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "-a", "dify_redis_password", "ping"] + interval: 5s + timeout: 5s + retries: 30 + networks: + - dify + + weaviate: + image: semitechnologies/weaviate:1.19.0 + container_name: dify-weaviate + restart: unless-stopped + environment: + QUERY_DEFAULTS_LIMIT: 25 + AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: "true" + PERSISTENCE_DATA_PATH: /var/lib/weaviate + DEFAULT_VECTORIZER_MODULE: none + CLUSTER_HOSTNAME: node1 + volumes: + - weaviate_data:/var/lib/weaviate + networks: + - dify + +networks: + dify: + driver: bridge + +volumes: + dify_storage: + postgres_data: + redis_data: + weaviate_data: \ No newline at end of file diff --git a/frameworks/Dify/1.14.1/dify-entrypoint.sh b/frameworks/Dify/1.14.1/dify-entrypoint.sh new file mode 100644 index 0000000..da77948 --- /dev/null +++ b/frameworks/Dify/1.14.1/dify-entrypoint.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROLE="${ROLE:-${MODE:-api}}" + +case "$ROLE" in + api|worker|beat) + export MODE="$ROLE" + cd /app/api + exec /bin/bash /app/api-entrypoint.sh + ;; + + web) + cd /app/web-root + export PORT="${PORT:-3000}" + + if [ -x ./entrypoint.sh ]; then + exec ./entrypoint.sh + fi + + if [ -f ./server.js ]; then + exec node ./server.js + fi + + if [ -f ./targets/next/server.js ]; then + exec node ./targets/next/server.js + fi + + if [ -f ./package.json ]; then + exec npm run start + fi + + echo "Cannot find web startup file" + ls -lah /app/web-root + exit 1 + ;; + + plugin_daemon|plugin-daemon|plugin) + cd /app/plugin-daemon-root + + export SERVER_PORT="${SERVER_PORT:-5002}" + export PLUGIN_WORKING_PATH="${PLUGIN_WORKING_PATH:-/app/storage/cwd}" + export PLUGIN_STORAGE_LOCAL_ROOT="${PLUGIN_STORAGE_LOCAL_ROOT:-/app/storage}" + + mkdir -p "${PLUGIN_WORKING_PATH}" "${PLUGIN_STORAGE_LOCAL_ROOT}" + + if [ -x ./entrypoint.sh ]; then + exec ./entrypoint.sh + fi + + if [ -x ./dify-plugin-daemon ]; then + exec ./dify-plugin-daemon + fi + + if [ -x ./plugin_daemon ]; then + exec ./plugin_daemon + fi + + if [ -x ./main ]; then + exec ./main + fi + + echo "Cannot find plugin daemon executable" + ls -lah /app/plugin-daemon-root + exit 1 + ;; + + *) + echo "Unknown ROLE/MODE: $ROLE" + echo "Allowed: api, worker, beat, web, plugin_daemon" + exit 1 + ;; +esac diff --git a/frameworks/Dify/1.14.1/test.sh b/frameworks/Dify/1.14.1/test.sh new file mode 100644 index 0000000..fe4605b --- /dev/null +++ b/frameworks/Dify/1.14.1/test.sh @@ -0,0 +1,195 @@ +#!/bin/bash +set -e + +IMAGE="${1:?ERROR: 缺少镜像参数。用法: bash test.sh }" +DOCKER="${DOCKER:-docker}" + +echo "=== Dify OpenCloudOS 单镜像基础功能测试 ===" +echo "IMAGE: $IMAGE" +echo + +run_test() { + local name="$1" + shift + + echo -n "检查 ${name}... " + if $DOCKER run --rm "$@" "$IMAGE" >/tmp/dify-test.log 2>&1; then + echo "✓ 通过" + else + echo "✗ 失败" + echo "---- 日志 ----" + cat /tmp/dify-test.log + echo "------------" + exit 1 + fi +} + +# 1. 镜像基础命令 +run_test "容器基础启动" \ + --entrypoint bash \ + -e ROLE=api \ + "$IMAGE" \ + -lc "echo container-ok" + +# 2. python3 / Dify API 虚拟环境 +run_test "python3 运行环境" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + python3 --version + /app/api/.venv/bin/python3 --version + test -x /app/api/.venv/bin/python3 + " + +# 3. Dify 后端基础 import +run_test "Dify API import" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + cd /app/api + /app/api/.venv/bin/python3 - <<'PY' +import os +import sys + +print('python3:', sys.version) +import flask +import celery +import sqlalchemy +import redis + +print('flask:', flask.__version__ if hasattr(flask, '__version__') else 'ok') +print('celery:', celery.__version__) +print('sqlalchemy:', sqlalchemy.__version__) +print('redis:', redis.__version__) +print('dify api imports ok') +PY + " + +# 4. API 入口文件检查 +run_test "API 启动入口" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + test -f /app/api-entrypoint.sh + test -x /app/api-entrypoint.sh + test -d /app/api + test -f /app/api/app.py || test -f /app/api/app_factory.py || find /app/api -maxdepth 2 -name 'app.py' | grep -q . + " + +# 5. Worker / Beat 依赖检查 +run_test "Worker / Beat 基础依赖" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + cd /app/api + /app/api/.venv/bin/celery --version + /app/api/.venv/bin/python3 - <<'PY' +import celery +from celery import Celery + +app = Celery('dify-test') +assert app.main == 'dify-test' +print('celery basic ok') +PY + " + +# 6. Node.js / Web 基础检查 +run_test "Node.js / Web 运行环境" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + node --version + npm --version + test -d /app/web-root + cd /app/web-root + test -f package.json || test -f server.js || test -d targets + echo 'web files ok' + " + +# 7. Web 启动文件检查 +run_test "Web 启动文件" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + cd /app/web-root + + if [ -d ./web ]; then + cd ./web + fi + + if [ -x ./entrypoint.sh ]; then + echo 'found web entrypoint.sh' + elif [ -f ./server.js ]; then + echo 'found server.js' + elif [ -f ./targets/next/server.js ]; then + echo 'found targets/next/server.js' + elif [ -f ./package.json ]; then + echo 'found package.json' + else + echo 'no web startup file found' + pwd + ls -lah + exit 1 + fi + " + +# 8. Plugin Daemon 文件检查 +run_test "Plugin Daemon 文件" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + test -d /app/plugin-daemon-root + cd /app/plugin-daemon-root + + if [ -x ./entrypoint.sh ]; then + echo 'found plugin entrypoint.sh' + elif [ -x ./dify-plugin-daemon ]; then + echo 'found dify-plugin-daemon' + elif [ -x ./plugin_daemon ]; then + echo 'found plugin_daemon' + elif [ -x ./main ]; then + echo 'found main' + else + echo 'no plugin daemon executable found' + ls -lah + exit 1 + fi + " + +# 9. ROLE 参数分发逻辑检查 +run_test "ROLE 非法参数校验" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + set +e + ROLE=invalid-role /usr/local/bin/dify-entrypoint >/tmp/role-test.log 2>&1 + code=\$? + cat /tmp/role-test.log + test \$code -ne 0 + grep -q 'Unknown ROLE' /tmp/role-test.log + " + +# 10. CUDA 基础检查 +run_test "CUDA 编译环境" \ + --entrypoint bash \ + "$IMAGE" \ + -lc " + if command -v nvcc >/dev/null 2>&1; then + nvcc --version + else + echo 'nvcc not found' + exit 1 + fi + " + +# 11. GPU 运行时检查:有 GPU 环境则检查 nvidia-smi,没有则跳过 +echo -n "检查 GPU Runtime / nvidia-smi... " +if $DOCKER run --rm --gpus all --entrypoint bash "$IMAGE" -lc "nvidia-smi" >/tmp/dify-test.log 2>&1; then + echo "✓ 通过" +else + echo "⚠ 跳过或不可用" + echo "说明:当前 CI/宿主机可能未配置 NVIDIA Container Runtime,或没有 GPU。" +fi + +echo +echo "=== 所有基础测试通过 ===" \ No newline at end of file diff --git a/frameworks/Dify/1.14.1/test_result.png b/frameworks/Dify/1.14.1/test_result.png new file mode 100644 index 0000000000000000000000000000000000000000..219072ccee7c433511ccb11b8be6e9416871fdc7 GIT binary patch literal 27940 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