From cc31dee66a5466df16ffc07131f7ea06e4dfdef2 Mon Sep 17 00:00:00 2001 From: yuchengen Date: Fri, 29 May 2026 11:03:42 +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.1/Dockerfile | 61 ++++ frameworks/Dify/1.13.1/README.md | 317 ++++++++++++++++++++ frameworks/Dify/1.13.1/build.conf | 4 + frameworks/Dify/1.13.1/compose-template.yml | 195 ++++++++++++ frameworks/Dify/1.13.1/dify-entrypoint.sh | 73 +++++ frameworks/Dify/1.13.1/test.sh | 189 ++++++++++++ frameworks/Dify/1.13.1/test_result.png | Bin 0 -> 28125 bytes 7 files changed, 839 insertions(+) create mode 100644 frameworks/Dify/1.13.1/Dockerfile create mode 100644 frameworks/Dify/1.13.1/README.md create mode 100644 frameworks/Dify/1.13.1/build.conf create mode 100644 frameworks/Dify/1.13.1/compose-template.yml create mode 100644 frameworks/Dify/1.13.1/dify-entrypoint.sh create mode 100644 frameworks/Dify/1.13.1/test.sh create mode 100644 frameworks/Dify/1.13.1/test_result.png diff --git a/frameworks/Dify/1.13.1/Dockerfile b/frameworks/Dify/1.13.1/Dockerfile new file mode 100644 index 00000000..9dab12a6 --- /dev/null +++ b/frameworks/Dify/1.13.1/Dockerfile @@ -0,0 +1,61 @@ +# syntax=docker/dockerfile:1.7 + +ARG DIFY_VERSION=1.13.1 +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.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/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.1/README.md b/frameworks/Dify/1.13.1/README.md new file mode 100644 index 00000000..734ce435 --- /dev/null +++ b/frameworks/Dify/1.13.1/README.md @@ -0,0 +1,317 @@ +# Dify on OpenCloudOS 9 + +## 基本信息 + +* **框架版本**:Dify v1.13.1 +* **基础镜像**:`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.1 . +``` + +也可以显式指定版本: + +```bash +docker build \ + --build-arg DIFY_VERSION=1.13.1 \ + --build-arg PLUGIN_DAEMON_VERSION=0.5.4-local \ + -t oc9-dify:1.13.1 . +``` + +## 使用示例 + +查看 Python 版本: + +```bash +docker run --rm oc9-dify:1.13.1 \ + python --version +``` + +查看 Dify 后端虚拟环境 Python: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.13.1 \ + -c "/app/api/.venv/bin/python --version" +``` + +查看 Node.js 版本: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.13.1 \ + -c "node --version" +``` + +查看角色启动入口: + +```bash +docker run --rm oc9-dify:1.13.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.13.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.13.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.13.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.13.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.13.1 +``` + +## docker-compose 示例 + +```yaml +services: + api: + image: oc9-dify:1.13.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.13.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.13.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.13.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.13.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.13.1` 对应 `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.1/build.conf b/frameworks/Dify/1.13.1/build.conf new file mode 100644 index 00000000..6eb94d1c --- /dev/null +++ b/frameworks/Dify/1.13.1/build.conf @@ -0,0 +1,4 @@ +# Dify 1.13.1 [Api,Web,Plugin]on OpenCloudOS 9 (GPU) +IMAGE_NAME=oc9-dify +IMAGE_TAG=1.13.1 +GPU_TEST=false \ No newline at end of file diff --git a/frameworks/Dify/1.13.1/compose-template.yml b/frameworks/Dify/1.13.1/compose-template.yml new file mode 100644 index 00000000..ef3b08a7 --- /dev/null +++ b/frameworks/Dify/1.13.1/compose-template.yml @@ -0,0 +1,195 @@ +name: dify-opencloudos + +x-dify-image: &dify_image oc9-dify:1.13.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.13.1/dify-entrypoint.sh b/frameworks/Dify/1.13.1/dify-entrypoint.sh new file mode 100644 index 00000000..da779489 --- /dev/null +++ b/frameworks/Dify/1.13.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.13.1/test.sh b/frameworks/Dify/1.13.1/test.sh new file mode 100644 index 00000000..b3902748 --- /dev/null +++ b/frameworks/Dify/1.13.1/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.1/test_result.png b/frameworks/Dify/1.13.1/test_result.png new file mode 100644 index 0000000000000000000000000000000000000000..b602a979bdc4ff38a249f7e13b2777a3df17975b GIT binary patch literal 28125 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 C8h$GP literal 0 HcmV?d00001 -- Gitee