From d7db5a9ae6c206ac43092756252f7adc1f72cb41 Mon Sep 17 00:00:00 2001 From: yuchengen Date: Fri, 29 May 2026 10:39:55 +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.2/Dockerfile | 63 ++++ frameworks/Dify/1.14.2/README.md | 317 ++++++++++++++++++++ frameworks/Dify/1.14.2/build.conf | 4 + frameworks/Dify/1.14.2/compose-template.yml | 195 ++++++++++++ frameworks/Dify/1.14.2/dify-entrypoint.sh | 73 +++++ frameworks/Dify/1.14.2/test.sh | 195 ++++++++++++ frameworks/Dify/1.14.2/test_result.png | Bin 0 -> 28122 bytes 7 files changed, 847 insertions(+) create mode 100644 frameworks/Dify/1.14.2/Dockerfile create mode 100644 frameworks/Dify/1.14.2/README.md create mode 100644 frameworks/Dify/1.14.2/build.conf create mode 100644 frameworks/Dify/1.14.2/compose-template.yml create mode 100644 frameworks/Dify/1.14.2/dify-entrypoint.sh create mode 100644 frameworks/Dify/1.14.2/test.sh create mode 100644 frameworks/Dify/1.14.2/test_result.png diff --git a/frameworks/Dify/1.14.2/Dockerfile b/frameworks/Dify/1.14.2/Dockerfile new file mode 100644 index 00000000..af621836 --- /dev/null +++ b/frameworks/Dify/1.14.2/Dockerfile @@ -0,0 +1,63 @@ +# syntax=docker/dockerfile:1.7 + +ARG DIFY_VERSION=1.14.2 +ARG PLUGIN_DAEMON_VERSION=0.6.1-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.2 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.2/README.md b/frameworks/Dify/1.14.2/README.md new file mode 100644 index 00000000..11be8bcd --- /dev/null +++ b/frameworks/Dify/1.14.2/README.md @@ -0,0 +1,317 @@ +# Dify on OpenCloudOS 9 + +## 基本信息 + +* **框架版本**:Dify v1.14.2 +* **基础镜像**:`opencloudos/opencloudos9-cuda-devel:12.8` +* **Python 版本**:3.11 +* **CUDA 版本**:12.8 +* **Node.js 版本**:22 +* **Plugin Daemon 版本**:`0.6.1-local` +* **镜像模式**:单镜像,多角色容器运行 + +该镜像合并了以下 Dify 组件: + +```text +api +worker +beat +web +plugin_daemon +``` + +不包含以下外部依赖: + +```text +PostgreSQL +Redis +Nginx +向量数据库 +sandbox +ssrf_proxy +``` + +## 构建 + +```bash +docker build -t oc9-dify:1.14.2 . +``` + +也可以显式指定版本: + +```bash +docker build \ + --build-arg DIFY_VERSION=1.14.2 \ + --build-arg PLUGIN_DAEMON_VERSION=0.6.1-local \ + -t oc9-dify:1.14.2 . +``` + +## 使用示例 + +查看 Python 版本: + +```bash +docker run --rm oc9-dify:1.14.2 \ + python --version +``` + +查看 Dify 后端虚拟环境 Python: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.14.2 \ + -c "/app/api/.venv/bin/python --version" +``` + +查看 Node.js 版本: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.14.2 \ + -c "node --version" +``` + +查看角色启动入口: + +```bash +docker run --rm oc9-dify:1.14.2 +``` + +默认启动: + +```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.2 +``` + +### 启动 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.2 +``` + +### 启动 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.2 +``` + +### 启动 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.2 +``` + +### 启动 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.2 +``` + +## docker-compose 示例 + +```yaml +services: + api: + image: oc9-dify:1.14.2 + 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.2 + 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.2 + 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.2 + 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.2 + 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.2` 对应 `0.6.1-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.2/build.conf b/frameworks/Dify/1.14.2/build.conf new file mode 100644 index 00000000..b09a0f31 --- /dev/null +++ b/frameworks/Dify/1.14.2/build.conf @@ -0,0 +1,4 @@ +# Dify 1.14.2 [Api,Web,Plugin]on OpenCloudOS 9 (GPU) +IMAGE_NAME=oc9-dify +IMAGE_TAG=1.14.2 +GPU_TEST=false \ No newline at end of file diff --git a/frameworks/Dify/1.14.2/compose-template.yml b/frameworks/Dify/1.14.2/compose-template.yml new file mode 100644 index 00000000..ed19f915 --- /dev/null +++ b/frameworks/Dify/1.14.2/compose-template.yml @@ -0,0 +1,195 @@ +name: dify-opencloudos + +x-dify-image: &dify_image oc9-dify:1.14.2 + +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.2/dify-entrypoint.sh b/frameworks/Dify/1.14.2/dify-entrypoint.sh new file mode 100644 index 00000000..da779489 --- /dev/null +++ b/frameworks/Dify/1.14.2/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.2/test.sh b/frameworks/Dify/1.14.2/test.sh new file mode 100644 index 00000000..9f0a51ec --- /dev/null +++ b/frameworks/Dify/1.14.2/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.2/test_result.png b/frameworks/Dify/1.14.2/test_result.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e7ff4fa74cfd716b9ff3c1a6b1aff1ff53b22a GIT binary patch literal 28122 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

%Q${O!*B;~^>phAOT@tQhQ9CO)!)MO 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 NL{VL#M9wt${{hSb?;`*J literal 0 HcmV?d00001 -- Gitee