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