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