From 5dbdfef76dbd31ba7d6f53d6a1d9b5cb3ba3cb47 Mon Sep 17 00:00:00 2001 From: yuchengen Date: Fri, 29 May 2026 11:09:53 +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.0/Dockerfile | 63 ++++ frameworks/Dify/1.12.0/README.md | 317 ++++++++++++++++++++ frameworks/Dify/1.12.0/build.conf | 4 + frameworks/Dify/1.12.0/compose-template.yml | 195 ++++++++++++ frameworks/Dify/1.12.0/dify-entrypoint.sh | 73 +++++ frameworks/Dify/1.12.0/test.sh | 189 ++++++++++++ frameworks/Dify/1.12.0/test_result.png | Bin 0 -> 28390 bytes 7 files changed, 841 insertions(+) create mode 100644 frameworks/Dify/1.12.0/Dockerfile create mode 100644 frameworks/Dify/1.12.0/README.md create mode 100644 frameworks/Dify/1.12.0/build.conf create mode 100644 frameworks/Dify/1.12.0/compose-template.yml create mode 100644 frameworks/Dify/1.12.0/dify-entrypoint.sh create mode 100644 frameworks/Dify/1.12.0/test.sh create mode 100644 frameworks/Dify/1.12.0/test_result.png diff --git a/frameworks/Dify/1.12.0/Dockerfile b/frameworks/Dify/1.12.0/Dockerfile new file mode 100644 index 0000000..a43a04a --- /dev/null +++ b/frameworks/Dify/1.12.0/Dockerfile @@ -0,0 +1,63 @@ +# syntax=docker/dockerfile:1.7 + +ARG DIFY_VERSION=1.12.0 +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.0 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.0/README.md b/frameworks/Dify/1.12.0/README.md new file mode 100644 index 0000000..20cb2c9 --- /dev/null +++ b/frameworks/Dify/1.12.0/README.md @@ -0,0 +1,317 @@ +# Dify on OpenCloudOS 9 + +## 基本信息 + +* **框架版本**:Dify v1.12.0 +* **基础镜像**:`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.0 . +``` + +也可以显式指定版本: + +```bash +docker build \ + --build-arg DIFY_VERSION=1.12.0 \ + --build-arg PLUGIN_DAEMON_VERSION=0.5.3-local \ + -t oc9-dify:1.12.0 . +``` + +## 使用示例 + +查看 Python 版本: + +```bash +docker run --rm oc9-dify:1.12.0 \ + python --version +``` + +查看 Dify 后端虚拟环境 Python: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.12.0 \ + -c "/app/api/.venv/bin/python --version" +``` + +查看 Node.js 版本: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.12.0 \ + -c "node --version" +``` + +查看角色启动入口: + +```bash +docker run --rm oc9-dify:1.12.0 +``` + +默认启动: + +```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.0 +``` + +### 启动 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.0 +``` + +### 启动 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.0 +``` + +### 启动 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.0 +``` + +### 启动 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.0 +``` + +## docker-compose 示例 + +```yaml +services: + api: + image: oc9-dify:1.12.0 + 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.0 + 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.0 + 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.0 + 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.0 + 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.0` 对应 `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.0/build.conf b/frameworks/Dify/1.12.0/build.conf new file mode 100644 index 0000000..1ab2ff0 --- /dev/null +++ b/frameworks/Dify/1.12.0/build.conf @@ -0,0 +1,4 @@ +# Dify 1.12.0 [Api,Web,Plugin]on OpenCloudOS 9 (GPU) +IMAGE_NAME=oc9-dify +IMAGE_TAG=1.12.0 +GPU_TEST=false \ No newline at end of file diff --git a/frameworks/Dify/1.12.0/compose-template.yml b/frameworks/Dify/1.12.0/compose-template.yml new file mode 100644 index 0000000..4eed315 --- /dev/null +++ b/frameworks/Dify/1.12.0/compose-template.yml @@ -0,0 +1,195 @@ +name: dify-opencloudos + +x-dify-image: &dify_image oc9-dify:1.12.0 + +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.0/dify-entrypoint.sh b/frameworks/Dify/1.12.0/dify-entrypoint.sh new file mode 100644 index 0000000..da77948 --- /dev/null +++ b/frameworks/Dify/1.12.0/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.0/test.sh b/frameworks/Dify/1.12.0/test.sh new file mode 100644 index 0000000..b390274 --- /dev/null +++ b/frameworks/Dify/1.12.0/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.0/test_result.png b/frameworks/Dify/1.12.0/test_result.png new file mode 100644 index 0000000000000000000000000000000000000000..a41fc41d67a503977a2d321314bd5e55c355b0b2 GIT binary patch literal 28390 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 literal 0 HcmV?d00001 -- Gitee