From 264bdf043f5436bd92eab48e369e3c3615f3c6e9 Mon Sep 17 00:00:00 2001 From: yuchengen Date: Fri, 29 May 2026 11:11:52 +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.11.3/Dockerfile | 58 ++++ frameworks/Dify/1.11.3/README.md | 317 ++++++++++++++++++++ frameworks/Dify/1.11.3/build.conf | 4 + frameworks/Dify/1.11.3/compose-template.yml | 195 ++++++++++++ frameworks/Dify/1.11.3/dify-entrypoint.sh | 73 +++++ frameworks/Dify/1.11.3/test.sh | 189 ++++++++++++ frameworks/Dify/1.11.3/test_result.png | Bin 0 -> 28016 bytes 7 files changed, 836 insertions(+) create mode 100644 frameworks/Dify/1.11.3/Dockerfile create mode 100644 frameworks/Dify/1.11.3/README.md create mode 100644 frameworks/Dify/1.11.3/build.conf create mode 100644 frameworks/Dify/1.11.3/compose-template.yml create mode 100644 frameworks/Dify/1.11.3/dify-entrypoint.sh create mode 100644 frameworks/Dify/1.11.3/test.sh create mode 100644 frameworks/Dify/1.11.3/test_result.png diff --git a/frameworks/Dify/1.11.3/Dockerfile b/frameworks/Dify/1.11.3/Dockerfile new file mode 100644 index 0000000..ed49e62 --- /dev/null +++ b/frameworks/Dify/1.11.3/Dockerfile @@ -0,0 +1,58 @@ +ARG DIFY_VERSION=1.11.3 +ARG PLUGIN_DAEMON_VERSION=0.5.2-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.11.3 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.11.3/README.md b/frameworks/Dify/1.11.3/README.md new file mode 100644 index 0000000..c42ceb3 --- /dev/null +++ b/frameworks/Dify/1.11.3/README.md @@ -0,0 +1,317 @@ +# Dify on OpenCloudOS 9 + +## 基本信息 + +* **框架版本**:Dify v1.11.3 +* **基础镜像**:`opencloudos/opencloudos9-cuda-devel:12.8` +* **Python 版本**:3.11 +* **CUDA 版本**:12.8 +* **Node.js 版本**:22 +* **Plugin Daemon 版本**:`0.5.2-local` +* **镜像模式**:单镜像,多角色容器运行 + +该镜像合并了以下 Dify 组件: + +```text +api +worker +beat +web +plugin_daemon +``` + +不包含以下外部依赖: + +```text +PostgreSQL +Redis +Nginx +向量数据库 +sandbox +ssrf_proxy +``` + +## 构建 + +```bash +docker build -t oc9-dify:1.11.3 . +``` + +也可以显式指定版本: + +```bash +docker build \ + --build-arg DIFY_VERSION=1.11.3 \ + --build-arg PLUGIN_DAEMON_VERSION=0.5.2-local \ + -t oc9-dify:1.11.3 . +``` + +## 使用示例 + +查看 Python 版本: + +```bash +docker run --rm oc9-dify:1.11.3 \ + python --version +``` + +查看 Dify 后端虚拟环境 Python: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.11.3 \ + -c "/app/api/.venv/bin/python --version" +``` + +查看 Node.js 版本: + +```bash +docker run --rm --entrypoint bash oc9-dify:1.11.3 \ + -c "node --version" +``` + +查看角色启动入口: + +```bash +docker run --rm oc9-dify:1.11.3 +``` + +默认启动: + +```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.11.3 +``` + +### 启动 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.11.3 +``` + +### 启动 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.11.3 +``` + +### 启动 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.11.3 +``` + +### 启动 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.11.3 +``` + +## docker-compose 示例 + +```yaml +services: + api: + image: oc9-dify:1.11.3 + 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.11.3 + 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.11.3 + 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.11.3 + environment: + ROLE: web + CONSOLE_API_URL: http://localhost:5001 + APP_API_URL: http://localhost:5001 + ports: + - "3000:3000" + + plugin_daemon: + image: oc9-dify:1.11.3 + 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.11.3` 对应 `0.5.2-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.11.3/build.conf b/frameworks/Dify/1.11.3/build.conf new file mode 100644 index 0000000..7609285 --- /dev/null +++ b/frameworks/Dify/1.11.3/build.conf @@ -0,0 +1,4 @@ +# Dify 1.11.3 [Api,Web,Plugin]on OpenCloudOS 9 (GPU) +IMAGE_NAME=oc9-dify +IMAGE_TAG=1.11.3 +GPU_TEST=false \ No newline at end of file diff --git a/frameworks/Dify/1.11.3/compose-template.yml b/frameworks/Dify/1.11.3/compose-template.yml new file mode 100644 index 0000000..bff7ca7 --- /dev/null +++ b/frameworks/Dify/1.11.3/compose-template.yml @@ -0,0 +1,195 @@ +name: dify-opencloudos + +x-dify-image: &dify_image oc9-dify:1.11.3 + +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.11.3/dify-entrypoint.sh b/frameworks/Dify/1.11.3/dify-entrypoint.sh new file mode 100644 index 0000000..da77948 --- /dev/null +++ b/frameworks/Dify/1.11.3/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.11.3/test.sh b/frameworks/Dify/1.11.3/test.sh new file mode 100644 index 0000000..b390274 --- /dev/null +++ b/frameworks/Dify/1.11.3/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.11.3/test_result.png b/frameworks/Dify/1.11.3/test_result.png new file mode 100644 index 0000000000000000000000000000000000000000..e408dfcb5ce8e4aaef40a1a0201717faa8cace59 GIT binary patch literal 28016 zcmd43WmH^Ev;~NJaA_QZ1PIo+2Y2`2>Be1xyIW%k4k5U^J0U|;Dvm2q-+ivBu8KDTYM{>WBx<7E> z3u2TXQ|Y)M^O0YM57D0IL3=QL9)>H4MjM9f>iIB%`t+wHTAL^#*!pVSmbJi zzx&~cyIM|RxY`t?;%qG7!^F?Ep1|8lQf83}3T#yB6-mv9n~RZ1k1NZ~FKUqlW{EFI zOJCSP8H;Vm{5I(lD8=qJ*9@pH6q)|lwWMyPF`V^LUWW)IUXp7HzpGLMo zhC7+G?h2+4$*Xej6hO>X?A%cpH?wvT&pb8UgR4(8Yanfe>|;9_De&(!Fn;HeCy6P} zR&4Kfx7$~D&d^V?Y(9oLN>A2o_kE?+`=y@vZE)<4rX2b#`TNnZzuD!@WJWJ~Ee?NY z*EbOz&zLMgTYa4>GC7w#6*0JX**ZjSxUb~x`z(_!m+ol@Y~rHuKj7mRZ%aCk?U7fB$zS`5lJHt(Wz2tU4G$vn6z_+Eytqqy64dr z<2;+QXU`>`p${F;VI27ibE-dq@Sm&uJ%{~U+W_zLlI$W3?+*`Ed@2y>{ZqBtQe9<$ z7`AKcZ5~D;g0v)JX+JB&Gas#-N5Xp@P@Kb^A%gVijSN=`h8Q(jdG1xK z)uq2RvBbVG`A;;w!^;02aFLnE_A2#FqlVn{>vJVclT=_97mD?Y-z3pY`5xk(o* zj*EI}BjI_@buL{kj7KE0&`1ww?lpYrV4qwc&n>ayUu#oCNDGnsdd^v>EhY(Cy^)s^ zM7&+M>^bBr^e0je;SlGJiq7a`p3U*TZ`p9^EyAo-B@6)qklM|Y*^cBjQj>YCQJ%5^ zGLZ_TwvbY|^ul-h0_5Z$8wjMReKgmtuHC&Z^3tkZx+pp@@5Z~9f3zbX9nE7;o|b`k zpG`ILJGztzT&f(<=;0FKE;D;>ZiBEU`n9z)j6_;bU5J3eJPZ+V{8B)gZ<|r(La%;^ z&nAt8SA1{OswO7+(w{jQ(l zjICqwB48lKgP8(nZ6$$fq4L7GJtgVIdUv}KJHI-huXWXs;CIEf^TCE?6hjokiTGlN zE49{@lZ`pTLEpv8)-m6foWiep;x}+2TcE-P`yBZ;4(Ru`lg-u@oz?#LJm9cxq{KyQ z>fJ#2lfE}$FU7jMxjU;#T@yCFE>7&wU6D25Ior<(iC@HDgr@(CFYUBL};O)U|XV(`L zpR_3=0XC<6#*?mWp$5IPxiNh<&fM2#OS2r49nqSP-s!igCgNsJ%R8!qVY(K^+h`Ki zG%U~+X3sufD7zAT!NeMmDJ`T}RRSV*XPiz=ZBdu4I&73v^>F}){R=(cgsw()Se)2m ziET#ys>Tu)IojKE3!WnHv?3ggA6q;!BP*d&+aE4V$F{GH>;yhouic4$Ru^OCGqii73}A$%b{N6%~HcO)hWO z6u?OlB3a5UVM3aH^DZz&$5vbkld+laL0dd-$3Q(-vt<&SkX=3JW2gi-PfZrBB5sPo zm@{I^s4*!I-453cFUjRkl0`3sh4w@l!*B$Ky#iQjd5mL<{wnI{$jg1B^LuR$RQz|Lu?x3yayLzd$i8ARud z&-4SiVHO@lmb0@9Hc)kgnX(|eD$?LAIP1H-&c|z=QByc6Hdusfq^|mpc&XZca43%^ zc-uhBmeG1;&<&!1()Eq!8mI|`McrEVUF#yRx_#2Ps8*HMo@?$|GQn`I8t(^2zK?vL z`A+xnNT2O8_4e()CRT$*JiAt#iRO&lhGsH~&}x=Fb7wM>v+ats=y%^XeqXH~QkP)( zE`;gofTE@>wE6u4AF3sD8=GC^1!C2apLcOb$qVJnz&fq;mr;9PJGG7?NWe#R2zW(< zj?OsSkG~gL;Wej!>fsHxdU<*DBvTgxHI^w9Ew75XG*}Jv435yA57>5DL(A^g(}btL zhJ(K^eV(o-Z4Tc_oAJ_raLrwh7u z%q~~$J1lH-z37o9o6e5s7})aSN@bGW6fo6-VWN5s@sZ;(*zPW+y_#@bYH~C@Z&Irf zmvkQ?rHs4IB?elMB@d}aMhIdOT9UrOD`ZS$xPIilQ$0WAK-E&4qmk+F?r3it0e1G%(%0iuFMIrx#Ns!Zy$HdF zB(hYyE@o0baf4$oO9nw*Z{95!wYh0%_JlW=9c`$q!O4lLGyC~5A8A7SZojrfi8qic z8sGlOy>j#%ZtrG-gZB>mQXD#Iv#R8=9nm35yBN~LGBMb#wijI@?v_p%SxGm@C`iah z`)MN6~``2{8)DxY7~N z-K^C%y0jDzF-flKTCB0;*5p04YWOb0NZ`0`#Lg5@Dt;)xOQl~;D!NB4a&R#a{7k}> z2Z+-9q5VW}?@!uFaO)k#0(T!QOy1HWS_CqVvNbk!K~eui&7&WV%0YlH<*|2;e3?T+ zdj+EXOqc*>X*Z;ox$ZO?F`^W-)ut*wn-)`@ya-B5g2Kgu_l1bW)64mjnHT+^$Z@+G^9ij(XXu{<#k-32BV8piT`&2mq4<~;WKp9tC=dzyVb8?KwS zHv*Bg`jqtZj2Qhb+>ktnt4=_yq%xS~##Z{1V$vIAFQ;C5OTIA}G9L&Jem+P)G?Ed? zbjQgj7N0KVMYv>?E*rTwH?;)MxaIrP;$7LY#=54iILF)inA)(zxQ!hx3PWq8MuSw8a_sDY1h(TwSqb(3vm7C?+p zTt@@Q2RXk*#>`YvF)Bfa?;ca_!0^YewOjM1YZ->NICW=?X+Ia zYX>||Yw;Akj=FClR6dw#;iNcvG-?Rv9IN^o6e%3PX<2BS$ktT~OKC5?YCfqtqk;fyzDCTy~l7oTc1<15KlZ1V1=qS7-Ff&oK147gvNIa`Zt`& zmj%~y?5CbTxXi(DDJQbS>5Qt^p<9TbXG@+tQx4Pj$TOA+hg-*p%&XP)CNec*6gSjo0V9YS#Q2r--SS-0ly3A%DtjLNw0L ztxTD&T%gKs@pv1-pBIeJ7!6g9d!+VEGc&ilvOh2GJ9C%o<<<*zt{s!O@;c?Odza3n znj~Ba)|_XqWQFy9KnMXIq-0!fW8{Y*1b1m2$x1wf56)3T)viar#dE{jZ59CH%Ab+_W?5xiXms5R0JoN}PE6R2*<+FGCP1~=I`5B));?P`~Jkh8^h+ zmpvZmAqZMM;egVKQ?C0)zr5yoEaXR&M$gNnmDs(%RctQlU_ja-(&R{0-)t6KtJWmG z2&sEcf%r0Ode@=|zwf=!r4uZw4H$a@j8M*Nc&gW$Lv6WzMPHUTyBEFgHnU^K&UN`P zq`2+|{n=6z$7Lx7A7#EH{2uFDY4J{2|5NiA49RnU|B4pDUs}Phe&R;w&KSf+WG}C7 zHu+TOh^3(9W@{IBIXE2WBe6*b3t1m+I3K*y)C|mRd^R|@iJ4Xd%R(xXt>yYC%^p(M z5j*lm(dO#yzy(;Wi@f&{)R5^PSPaFiQ76u%aEwQ`^GJe{m)?ndBkKKwsEq`0%!fAO zXC|r}2cnl*c!X$~edL6#HKtuzOyLuKrv6GCaJ#+}*A;8bW!w?Bj2?+Eqoz9DgJHbL zzXUqaFIKG%c##-;{yM-*{9sWe!YJ9}LYtzM1Nk=&fI0Ylt=#*Aek!=F%)IH*w@&h}@i!{|;9CnEw%||{Q$FBF@BNg2{iuVbFKsL&TGB>&(s+$g0TlyT< z7}P2762Z6I8{OYTiaoPRn%eTdpgdKvF2-ZQmxJe5m2*1p`-t^N2W)JTHRn)nL% zsGDL%5cngH#EfHD+hcR4bk*Fg?fWD4#WV8B&NQvSOO!FVE(AOs94GblqX1r~=aarv zP*~Eg3$A6`ueE83yJ2OySE>F{+Q^y6D4xSB*0FWk+lxgvJ3p{FHJk&fhHDtbkgsXi zC4SvA13y1GOKMY}Vdj(A@}uC$>}|@}tGvy0tIF*bmzv%n#G|#@S@J-lUpU_)-;w{y zCRj)J9ul7={wkH};?8)c1f*Rn>DC%Ta{!|3_bP52=NeF7S)WQJUOV%%b=6e+w;g?X z-}p-*vo1j#V%%bitLoXHUH$E<;+G~diQhB(2HEZj0@a|OKOPWg8>5caAco>V*=qSS zRa!dM{)IeN{P2Ryy~iNa5uq78JptOVbF_ZIwOG$j-rrZj+wlR$v|F4S& zv+kG)!T&71Q#dB*(Cnx7H`GgkG5~V=E0}>1GfN_#U}@K(wfM%vTuU!&q`sZjWNePL zYh0)}(JHSZwIY|(aTa?bZo9BSH_z9=T9rf%2lRAPGbqr$4kcC{_yqs!BsDzDkOG;f~sQekjmt2F)c&vu}EOZrYc zOAjXi4hdq}Yoiriw1p&|YHx7W;U}%B##XLSXr5K9M>#V@Q{CfgTQP|w3XE`RUJq$E z=amnFDt!?uax23#7FRMt{fgh(z=PZ717am2F>>>&l4Sh5wESDWAS}!&Y4zTlnkwRP^KYGHwfICqnEt$L76YBW2Dbs@;c5jja((#9AUSHdW> z@w1331=RTngL}8-!N2sYW~{#LkRsxn#8QOODQQ_Oep<3BYCSJUq~+4dwcI>%JJjH0 zEH~3Ipn+<(%{Bd=_)1RvH{hrKRIJcS3)UNYy?|({cP9q#6#63;YRdU(41BoOvp%u6 zgwH?36dPQL{TTsNXOc;F9>&&( z&;KX_gnqGpW+XGs)MOp(zW-L$z zhLT_Fi`bW?x5$O;r6euolSM5h?_^c{Shq(l=r`HvMs(BY+M5-tP)}>`bQ$Ta5nWOY zr;AH?v|kHWZlaN%UG%4mI=Hfy#$qjK&4iFUkP(9c;)hXEiq_Z~;=hmrSg(!$`Qx`#tA8xqg5+0y)B7L>0aW{H4q_#vj98 z@p^r?m33 z1iM4To&sQ}uIhCH9D+me7R1tdt1MLS6VIsIEDxZ1EOl2mM(mRjmZ)~tNNY`jRB738wYKW z8!n6mY;B3Z=G~gww=pJ3wl&zVcV!w+6>0tu9h0AKV7JxAAc1}nCDBr1`)3zGmLLA`(Rb zbsBc`v>*e<9yEHLGu%xSrsH_pFW{DvOhU@GXzJUMSCX_uoplV=V_&M{|Mrt$;`Wz< zDEKH8mndpMwir|)w7>LvEks5~2ouhzQij3}GQkxCb0zNI0q51*05OUXCR6yoS!Fs* z(8cY+a)kN2;?bo55Q-8i)7>tMw=mx|Wki<=WJ_bt{{;pT=iOwoAh(^*c))9@OrSV> zJLE16UcTk(P+TgCuI;J%)eumaJL0JEynkV@s1ltpNd{9cG|TK08!otZ`)iWaH5q_K zM#DV|h#+*;91aNfkL=U952nlEkIELl@Qv z0fUe|?N#y1p^C~{_a6DOdp0Y&?bm>4|Tb=3re2Hb9k*)8lXN(l(1=g3{uc6y~^z>*_MoF*w%eTBBK2Hf&A?B8;U%b0XGORZ7vj)m?j{WaV*DA zYLmx@7~&xf5nMJ`x7Xq={7LEt8eKv2DW|g;q`w$QSBaPwQPO>Tws6DA|3uZq354L5 zP3<|RczZ;^bVK167CKGToDB2x1|lpL9M>jIymkwgqi!DI0@tR)jw0q`JJ5Xu3cpQE z-Pv6k%KFl;7sh!nphm62V`qlui=7v!&&pKF2LO`s7JWE8PuR)Y7J++Uvl zj;fRL?!{6%ko5pl2ZJDg7aiA??tu#EAwtDr(cyi2w8)Ve!j_|YIj-)Mcqd8g|WqsEOO$Gte|(|>=g^~Tn1|4hdX2iub- z924#`UtL`dZ7KDVAsh@Q9lnUjayPb6x~Y;c`g#3w^j6GmnGn3=+O^x3{BcBm-Ej1> zLBlW!lhy~P-O;_l^{KChESkabdIS*gNayr4$h#FUAmitC{UEk@uypS@wE6^8vpHk? z!tXmD#}=ws2+}jg=mF(CwNb5gxhBc z^k7}nCARw(P29AS_flG^aM5(e&|5%fgfK}-LSqwiRXTd>wY7w2S@z`YJ6u)g74f?T za~sEo%Zfp5Vsljs7doMLRx1p)Kh%Q^rdg+i!vcw%S;c`S!z2~F!2T+C{(*uzHDP3! z6-qTFVN>{4g=;RB`)|F1Mw>bY!WzMonG#>6iPK;8!AisSzzeGtBm^>UG;H6j#lF#M z3P%0Rrn%DkIsrlF>!Ox*!>a8U`&H7uvPNpk$|UohnpqZI2=h+2aFv8o?R_-gLxa6{ zzPAJ?nQ)^Ph8wbmiu1}DYlQFN6a9q+je(TassN!%7=~{tV)wDIDTZ*Oo>e#`+Eujm zC^}xZs&||Tzn13PIhE0nc2-beub^UcwzA=XE1dZ#i8(kPYd#j=;U3)v`T1N%zF|}c z<}hgoVQn9;^@M)?7rLPRD4@UZaK6p5o$B?M{&Ym)_^U~V7F_TbuRa<_euw8UBZU#x zFRJ?9>Gu?b#7x*N?&0ti4D84qV(M;j>9@U~J2P!YxCY4FlPkpMtsYepOSpX?aOe=-Bf3rHMjRmlwfMVxW#>XM_kC}Fm)t)vUw=KCeU z-os7o^jd?!ew{Y1Gg*z(j{7YhO)8!08K(Vs!aU;ctC6&($t00|_A*Q*(hbi3IvvK~ zaly12UtUi-`}hcx8aMdC=IW}p5T-Fh5-%2-lQ|sOzw!Y0)am;`x-C9tzN;LBGPc{* z5tj)=rHsR33sfxS!n^OPWA-TZ>@~x}k_iv~9DM$B2dZVG=d^|SE>`ir(^#QFrLzIZ zAgrw)ip$00);#<5As%+sG#EUX$y&m-a*pc~e*tcY%mk7Y8Oj?&$o*f2t3yOd^pUJr ztpBZK4}MHt2^>fIhQrJ2{epkHERd2AtkzrSm+g>QJR3*x->0bmtr_kgY4-OU&}e_a ztN#zMA?E&vcZsMl<@oOkoS^>~xC8!`0Fr3laR0w}u=7ix2g{ZEzd_N4M*wc`Z+~C= zc?936M~31U z7vyB8ZXYoGUF09=q(tE7+Vr(HH?g80A{W_2q*=Fg4~dzVbv&6`KNS})(?c_fHfqK~ z#M9=3^Zj=4na6ZWjEC&Nr=#nc-@O)lkR9)~8oi6e-7cSG@@W`WG!U4HmyZeriK~To zGID2r!r_Y=+cC(A15AtCq!59#_FNq)1gk2P>_GOf&9jmuP_CH=7Dh%!wp`0(?Bk5$ zO}LCQx7Fp%v@}X=v7gxMw2`%6|1cOY6i`jxbj6@zc2Hg;g{*Mom5*Q+USkU}*yEwp zZTSU(M{kkI6M_l770Im1!Kmy~Ukndnsv68p5>JA4gZ*7^lg8-inSE??XMYRIF3cz7 zAL5?;1$o^zYeeD2k~tP(of|_?@kV{9)#9LEpHEgW-`POv17!{nInI8{u5HFbj&V%` z5^R=Bg-P6WGszo3#JMe8AA0ijiBEsj@$@gWC0ahpPD_O|QAkC+(fH1ya;!zrH1eiE z={-FMS!la#*r1JvN!5*Qvk$b=34^rhP6yZ=lR_Z+m=HD(V%=F^5wFOM;fHG|jqYO4 zR7P&=5c;=}wtP$h$WFLHytIVbsrgN-qG(F!` zSScHpvW#10z1~)8D{x9#l~-g_4a^^zV1%x&t?AvSTgrT}dTk+7BkCCQN>uZHs$0qX7#5dj1@dbdS@f;DlVl6y*i{TvNr@0`Uyc8~t#`FdA04j#>Qt z{Bm`sPDi#*Pfx#Vy{!e=1Cb(hfR-x}Cn8TI^uzm?;kT0E?6Hxq=RWo2#FFur)j{IM z`Vt_U=4A%Q=EqJWESuSxx=Po|i%(*};`-iVml=(cMYiJASJsx_I2ua?jbO77b#LS- zM`;>a>z#;Y)`i%}`a(8l&p$;2mAFNpCZiT*TAa?JP_Ty_kY0?TsKYL&g)p!z2l=pF zMUzzdrKUa&Y@*1O6<`EWn2oBpp(nxEIRebh^x=RG}|>vMAF|o z?u^L(2iAwG{-@CWU%POWreRwX*vP{R~lq+t} z9(zOmHUqR;-+FSpBNuFZH9N2{Kd;A5rfi$sBXAilv~WT}OLZ1hY=G81wao~J0EFi( zR4oxjZ1x7K8~K#`_h#>2VBT>5k`2ijeZsy%xjt@7mB58ERl!f@Ewp5%?MegsnV4+} zO+NfUM)2v)O?koDj<(p*67sc<7UHs0UBEm_*4F znf4TZ2{O=uk$l5M-fo`+VLWXgsLvRE^;4ZZR}#M_j53mpT<YBol6JrZsR;YBgCDniwUT1O?>e- z@Iu^^4}C^k5RT0wz?KUVftA_8bn)wn(zxPD!1~F^i2^0$`ojpBDieKe>hR2DmtB|NH?SrjRrlN)=lbL`sMVQ!4cQhndI;k#-xrML& zt(4`a7SBtUB4$dXGy_!$x{#W9WLldy^e0c*SfC+I-d&xZD0)5DzQM@zz=`@jZSiE$ zW>t9^xq`X6lk6v(g&rCUTqx!g=O@jqsLoN@7Ou47_0wq8Uv*_&VkN&d{PBK>dkhUv z(>FjpcPu4Ml{>D!gdIZ>WzeR1{EYR7?f{^6F3{ixFmeB3Un2>JE&E3Jp)D58rAR8Q zhw=K+88j;Als4BQmASO<8+VC0U#DQ4xveHyPP^nG>Qy$@qwH$Mx}b&6`;QKufve-3*DqeRvJ% zz(QMPk#@9&d+tDV9b;DYwz~%W<;_i|p;lca5!eU)z{TyZ3ZOKyPc-5Ew248)ur5h{YP^X+YyKaPDS{}%>jReA%97{+!1LQ`go>Il|UHZrFsDwB&B{Z6{f-g|EDU129L2eZMU0Uvo4Igo2e}Dg76e@3K_i>9VGCu*vwMQ{{R=?zXBIJWWz)<<@Gm&lOSR>Bw z8pxMT)QuyvH*z*Adv!~+P;IIbE+ZkIY9@u^#jkX6n$FkuvRO(Jabw3-hp~IK^CZuw z6&H1M2hv4#ZGj$K@l83ZPZ?~U;tsU5F{o&PW09v6KAo?YQI`EOGgk}PQI|8*ffPC0 z-I}ncM0t;(P~9Ntv*-r6WgkikkXsvq1e^Nxor^;9Gp$1%px{N5DMlp*c=K9JHQxds zaqQUw!XW7Hen53Mqq_6Je$KyQ(f>J+xYhCW^mbgVdLq|&Ut9hCG*mBw3L8owRy4aR zh=A;F8(kNa%Y;d`t2v%_@`}&9hzheE(i6(fIW~{E@1q8gFNK5k=T=%8JYN`~@PT!~ zv2b-Y3ygYJR;OZ05P%XAkOa7;6%gL1II;@4`UW@lE%*r#kGbig{m@yCgg8KP+X2q% zw=gi0oltD0t)hURTiaz=XLtAZ>GgI(0#1kUwPPU#p*7+Kw~;tKbvL`Q9DEjk@qA4O zt{@~b(e6Z)O!mMAX9mwS8MQ)?&0@a^qe{fC z3zI3)qZYwn^g~p?2MvP|B>2SfiTpNLwY5^43M36#TPkq$z_Z zA12snxFnw!NIy5EDNAImnrUJT8;d&=NLi8CB4uR6CQ+Hmn}tjtYolR2>`J9*r<62Gt0e2VX;1bV;jXhi6V!)F_m>yiZR3HL}9uf4ZOx z@2UPI?T-b%=qp|i#J zjfckNPjqQTLF2yfj~Spmb;Z5Gl&FLi_#+<-LP|UG5U`F9X3wf%L?SE#{-iqZ1$u(_ z`mAt3Xk+bM$rFcM6$3&ehy8)0(Zof}(03;h5}-xT3Ll=Xmr)@Y2)H&E`Ga@d1AtUy zzJenZk&H4U1k%-$E`}<=2<6`H#-G;n+<=Qn-IEcq7`L`0rZZ>7?jb0mblUQwB4Eum z<2jbyPpU;0Xl`#I?!s@e_Hg&`qN14nS$sWqaMVFAVMqphW416?$oo>q%Avv)?`ytQPgz z(EGi)<_hFBtW|wg>iDzhPE{Mn29EtcuL$5<83XjKoB?5NBKio${X?Kyb||Zg)Fp8# zWp?b;qyzVqtA8$NR+Z*GP2y>~cO2|QE~m91#ks9b^W=+i>uuMm3Tz2>qk5yl`MtSI zAvDD*4{Qr9MHMrcmi4Mw)@3j=)y5ghTh+D?N!pDDVudrRbJG0ISa_N=Qxc$}$|H7< zdS6O^YlHFFW>Z*R4ZA-b=l(@7e&5=OO@u-K6GyZIe!#y)dM^*T_cyD{b}zVp%LFAP z#c)5VdJxYw83QlY$tL)>Gi;LmlNLHKPpm7Y?RfD1+h)^ji-&~IA{Vd_{$k-X(WD@OLE#n4EqdHC7FHf z!J;o5ME@J4Dlw??@-j*Si$AFxQ-J``!NU0@8NSjINtnLQfTv!mb7nDhCY}_rVfpN) z5i0PYWkjtfGZqz{5L}L)9bETb?%D-}B?X+KYBW-tI784d>R+9t9Olo|ql^&E_+S1D za@NANckZHn6iWG(1~7G7ve^I%Oa6EJuvcXo=LQEOBl><8m(&h9gftq%Z}V<(K8FSw z2BzP}JRge=MOq(>2BKL~w?k_r*$;-Wp*66R9e;LfLJ=)=Je=p2KN}x0>NC(>-!0l~ zEQ${8Psx4*oBc~{Ee7aRC(Bg0JrTHKf$!Xp3wBnZPMq!yKyRRL0v~KWT706$R#OS$ zsAdce5lx=(eq;{+r!Vg)IsttMoC_W5giR^SFaXDc$b^dVNXw*6hw?m=K`{Z$+dnqjgbeY0zzZr2e~sy?j}9=6`KZKDfh37>2y6iJ&XQnaXuLMQeuHU z-)mrgA*@xP?CR{aCI&2nzS9jy*R`~?_{CYoRD91v`8OJofQx28xv6t*GlIS4g~!Xr|7*yi zL*_-%DKQX*p#j!lJp`aO$|35A^pm0IlMLz_&xrH@wBRH7??bu;Dgjzf>7rs0IY2Wj zJrSILyPAp^goQDf+2CVDE#R|ZRspcPQNZQRNSmgadTZy);6ws0(<;k9{j{anp#Qm! z$TE1;?Ub3S>n92L3Qd9q%tW%awKY((0|SPSbtd&J(8^dYDusU&K<#PrFCSers`Q&B z5`BGfTRuWq^^$cgp6fcR+#*JWPp71~(|t%_uXOr=Sjz>CGHXoiJ+6idQ1>@zFVJN_ z3#8m{t44VkTmF?8JHSM_EyikeN?Gk+Dt%95E>?t;-GodG#ynFxNZL_oWgIJV^uAP? z<&ymioP+htz$U`9@Aamw`Hkd$SH;KKgd}EGKRd)Og)(m&U7WS%zR}`6jfK&)YYw~{dER0o<{L2F^kIK z(gEd8G<(H8!X9L3Uu>rMAa)O4a3)JoWwOTSmkX9n2|6Lx`rg*x(-*WqzC`x}Dp#rY z5JHH=h8h+pClLkWh4EV`s_7{V4}XLq{Qzl2!zXXLUPx!cn$9`IhckN%2AnT&EG%{z z|8*OO8t|g)0^vIXri830BxVGf_AAkoQ>utWl^94lV?gWFqpyK+^9(TkY_XU;`xg%NSuwn{nnxkiy$5UPUn%z0F z)|>UR;%zktG5>?>Qm1CFH^e3tw2_>VI<*Fn?L!!`QKnDjRQ+-MjmcA=>5o(Sp?pF#8Xc`ClI;8) zBj@q?ygzU6S?WMsE^~I}ni=XC*D)O7(CuwULTQktFtIajcMTJ#wXmPG>K>ENYdiX7 z6YDCy2A%Gi928SlPxTF;1|j{`_suYZ^CC2 z3txuV^n=Hn5T+Ml#`jYHoUI(~W^?YWF$k1@=~Q0kWVsf1e&gB(xoRyA$>BrwehCw} zw*A6B^BUWb4==oxAvRnVI_>GrcL`G7W22F}XO|k{mbZUw@yR60#a;qMt-ry~e{#he zT5Y~Q#~4g}refDx?BOy~is0w(pAwpokkB~80_(N@JVnSd9FJt7SL6zSiC*He5KaQ(vsrFPw7=Tz}qDN*ET$G zZYGAQ@F%jO+m|KDY*naW>h>EO)fM5;)P?4_Am8%nEPAwl=Pv@S6;e+^?sU`(=)t6v zW46(oT6OrjID;z7e_S`dH=bk)KZ;%Zg(ebelxP}}|MOS&zGjf^?_GG($X|dauiQXb zquMVf z)cyd8Ia{=}XAfJ@C6c&V)Bb8_F!9p7j))Qh5Kb%dpP+-%-0B&ITxVFl4!?#K>GpE6 z5^2+{cL!c`1y8h@gUSIlyYB7$W)6M5CQq<1Oh>WZb)^k11i4kjvXbx5eUOx~HZJe2 z1cdgfoTnEjvCMQ8YvPu4A?T5=HN%$*=2#_sY^HV0dyalOg%=5(XV$YJyD!@$6;5?9 z=45q;TBd4i=EUeWO2yg5p(k3#{ai{&6o*g_PtA$Y1Rs6!e&BtBl%fO3V05?1e5tc% zw{NWc7fp|%Nx-TRWpNd#W$8V(cg%H*Rt+@>8t`CBGL95QjgSVp534(0aO5e8;PO4* zHoXD5h*lt1Bcu&oWkSnxONQcIO!y81a}~IB-{E_I9W-;+MdyLN>A*Ug0XvKI0ZSKR zF%4+clA6>WLngJn&A+qh9c_q)wF46GFUF?&Vt|x9zLoehpaFe@Rl5^og}-B>8u{A{ z2%3Is2F!_5(2{J>w_#f>(%bvPTBCmkghn)wO7j_u==IFA*_X~lO+%;|7<|%tmMCn= zJovCzf`jdTwr`unk(XQ-PnY#1ZNIs+*eH*S0+PG3Jg*fzu}zeTBqnraFTP;UUvIlQtl z%K)*xO2iuUdUF6PeblN7M!&w~L@CJAfvoqZ>;Ed}_hAcziNbY_#&DY8rWrW_lA7dg z*Bnwkyi8WbO)wZ(q z%$hy{Y@9jCR2UnA2VDX%G1*`*-5pU6TfQ(w{!;ppcRHthzf$)t@mpf#ZEQ{ECCe(m ztbipZC{Mo%?Rj-7sk{fP7Ldb=4gn~jLi6szz4ut|qBU7XckcoEPjDQVQ`lX=J+0V_ zH-Bied7Uk2Sd!bep`)#-i&3kR(jMk6&mZrakUZB|_m`ASUxB@wr_N{?XV$1595S_F zi8<|8=Suog5ZY=ntz`o_ivHNr*;foPdiz%J@Dwr&vl2}Xl^UoTnhNqwEpQZ={0=L9 z`dmjmQLzUMEl_nANoEmUOFgN1>!zXDWbrO3yul%O`d#9G>&hSK(f#^WvaEZ7K`Vko zJ!ZU;ym1|0b1+KKea~mGgI<;?&_LgID{Od#Z!C8DmuTRE1q8;(yR0>wxyO<3;Bxza zdEQa=Mo|tcfnHVg{Cy<-00?p*rTjz1DfV8tALe?S5H{Aqvp_X3+9_+%b3T@_J{vgM zt07;i0WUhr{S`9cCn<0w_5X>A7Z2B;3QXEC0Wwf!TQI5z);uMlULJt_$D+rrvoNWn zjODmzJx+-Ko$-3YH;J#bzhl3xn1{8F{4e%Rb!1<2an)Ez@J(Eb=%&1+(_WJ^2ymYb z!_i-}RxaKunMDyyJyzKiSjg)xYh=JRU{A;AtBb`bcVm$m&`&JN=~IjUP~FXq!hZ^I zBL~^6jyx@?0stRun`@{fN|UzI@;e$nsIy%2{H5}U_Dds;KR+$-neY;C4cwnWX9N3f z0wbDs;=o?--&ab|;<*^oZ1Q-YdU$j!_LdkRXUc@~CV+Zns?=(IYYW-*#cw>K-l0C; zm4i&AOQX52ji>JS#3xC}>^KGxe#1{aGQALeRCxAPyE4$}^>V4W{EpfK4y;-|-`U*k ziwr<~yLW%TBFAcDTb@+S%Z&}Hxk5XsM6)hOhCncp9Alp2Y7tKN?KWC{;^RPxQBrH- zXkG3KkO)Lsm7jd?{WLob9ue?dZ3NNIfh(Z@kDujP_Ses{m%zhM8($;Z>Z5VQA-WIzvjE5_P;NVcSQbh@B-~RCBIJ`%zu^I%SdOBfi zY>vNj#~c4|x8#iDMg<2w-x-UPIBOur;1H-KkqK{=OaOkOSaRLKvYaHui)?w>7f|~7 z`FRr1t5>hG?#m^724oek4a`=pfqmmtE=^@rP)!B%-YXi38q?P2u0=F<3}cMEu}T!` zu#R|U8?1ej(TYn1p1^5uP{=JFhPSTsOl0h{9?ETt)x`NXA}W|fdiO%#i*w(uqn5c8 z)Sy;xUoS_?M?)_Sdi|0*%Orcmmn&b?-k0W;I!U8Z&er=e1|Qj&X9jkHqvF4;uWoOB z*)e_!G$>HXle^082u-@$z|k`ay}bBNvqK^)r5Qk2SyC5iVrMm7;76`m@Lcg;P^Hlu zH-k9Lj-0dHpj&1Dz$p9PWK}rS8z0A)i(Tw||4&OrjO;~Weu2jR!d3NCD>q>v7N{S< zdCZsLQW2l50tNO1Jj4>;!44QL?%lm|Wr;r1WHJDvI24QV=m=zb5os5eOa`A2lZiq- z-^xQ*=n=0HE?(d)4rwRLNKl^Q9|LHbtb1~U?lIPr40LWM`m-eV2ljYGB*kympH!{DrBt#V4G(uM3Y z)DDiFw18OIYV`c{o-lZ@9v3_q!n=OnD&~Ctxz`bh;zqo;B@9vyd;dd{%fkH(ONot- zzT^BxyHlsFl#84^7kUGW&=Sswp~_vZX>~8)OHzedRp5*@_)l-O*x|Q^sJMb+gg`(b`#tMb(Dwnh>NrhHj)~kd%;Aq)R}W8FFYO1Vl=DNU4`@7|9_8 zK|o+AkuD`gkP?uRb}vBR@7>4#e(pc~nq$^tt!Lf$y07cJo=MRa<(<2~P#LY!GB(Y~ zA4LgmQjkvEj+$1bT1dWmj*!O{KXk3zt@l~u&wu>Wq=kb%Xdx)TFHouQTs4f5f(y>acsb&OZ=V zS-3q$0{lA!)-W#vJ+u3%!~PTzN$S4){BQgJm^bf1l!oA9njc`{rsCW6Qu_Z_54{>5 z@28s5yZ0={UUyM$qpg64h5b^c!GKT0P9j=L35BScx-Uy1%dbBmk=Y=TLU>P$ zC@FFGj%N-aby(!4tF62nD!tH~bXt9?k98p*LK>Xc?qE|^dKgfCG_6%fWgQMgB{TZB z+^ZUAtkN0g#<{S_KibqaB`xyoL~popOCr9-s9GBQzs?S^?VlD}4MZ-+CO_AAOdiGC zHr3a+u2CH)#JA5(?m}<5cSnL9c2pj6J9Vw!s;{1qd067At;cNdSY_T2I(jT;p7|av zB`SF#6E__8gB8Q6jISgGgukkzrAO{mJ@}lDb%s-Q>B&6MH$%+hbON*Jyj)%3xK+3m zAH$nVZtmU*&^8xAeJC-EchBN;97HtU$;@pA!f4HRy=+~(mvsA$ziHu7jsux6jNp7e zyFyjOJ${!16=^CDnt+}iQj@!O%R8DH*E5B?Ei3pT#nwx(GsiEIO`l1NllGrgOS#J7cn zfgGF{(PxIwyL4XzD&VJy@C?y}K`{iBokm~&G=i#UuSt+v-03{@5X#qPErl3Hm{WeG=RI)Ve2ZOT9WFm)KOt#it~ntMMUs`zoP$>W;~>_|Rn? z@cq^3%(1`W&`{mO64!$B|Bzi|(2dvj_ibVBGY61H;l!I^Hq5UjXK9l;|^ZD>5n5~7T9F`0duhGUp;kc*tEdnK} zLNuT|Uh9C{X5P$?3`}eOHx-Z>`$`2wX$qDvabv$sfe8=6pGH z2Xj3w6;kdTqKdgCuXy_u3vBa5AU|6jp66|B9Qt&;>l2MSb|i1es-nyvgXe_%IXDc< zqS~8<0LI${G(WcIuWd*wlB2^CFuBPmB5OXNLzWcDvN)SnH$&|bl9;fRYl>Uk$~iX= zHDDgGcS{h%q#0=9>dB`rnhXPezUlKKo@4Fv;lFkAvttq z;B4Xis{A8-4gp(FREJn@ISav-c=16~1jG0C3~F-0hzJtTgvY%D1NdZR?h1OMLTD@S zaffFym`fWN6+_i2cu~jA`mO7 zjYu@vm61#AxO{ISfYaEnz1jtO)d6eO66f~#^bLkdHkfQfO&?n!Xq@b0wEV^wdD>Ou zY2nfZJm(K(X}PJDFH*{YLay+`kP@24E*|?YFcxy<-mE&BZL5ydUgo7@q&N9y4jAh| zCLLmYju`v_0O$FB2vh_9xWDDdzQ#)JBzy63#g*KsP_)L3EjK1xhy_u*U13nABZhV$ z5U%tvwCPj&(k`OujI)}g9@y6Fded&jAz5K+NLXz>wiGGVfbeA$~gU4>9$oo0DEmc=SOLvkW>V0H=_7ZXEUf)n19(At%NEa z9V;z$lX!_q;Vlf?At74D$Iow!4_#e)5hp(har&0Z$XOxLz7T)vS4TSKA#F7eYOw+q zHR`V|JzO>a(H;Y7L@aYde`!0#V&8oKg9v3f0uPo?D?8DRGi0OiWvcaSY!`nxFK*AI zi%saQai#LC(OH7V$xkrKfj1yOBM6Vf{T2I+cy!P+oZHrm{BEPR@Y5!B9|D#DdbD)kBCl=6;p3l}9|6%H^ zUfq=Hf)_8AWulO6m`&T$rk#)K059!bQ+v+41t`zh^Kg=i+a04VZ8tjcgRA=dsLp+p z9OZTA*OT`ra%RSIJ{As*~R(j9@sE{FSuH;#$YQchk$)HoAOwn@KN)B)!G_SaPgOHV|>`#)#e)&~qX zIeui%$aC)LUrje^j}O_`gu10x_1xM?z4??SQiDDF<+lEzH&_HbZoQnebr}d-6KHL8 z4NZ!U=5)RXr2+sGTByHXio zzgDRDG#Z6TiyH_>Wa}3JUZg2r-K%8GND!v<<$z3z(q~+-vt@WW@%{fL-M zm`xz1)LHkgi+BE!{~B!@-mtc04W{p(TWS}9e8w_3A9Bh8jsfhOY2k(s74~1J#*GG6 zD#*u!jt*8o+nd)Uc!?v6->J*oQ`rO6PyCl2SKjyZE7WzAq5pZ%@oQx4eY^g0dbZu@@fBhVR zOf`PpVb2+|`ZDYYy@7 z(N11o$us91d?c%jV=(6-$zxBP1eObz6>DxnCoO5tbe@dEnEYJ9?0DHk23*BLxyKMG zCxN$%>86Y>EnkJ7@L)BQxbnG>!A@H!9PQe3#x>gk4cNhooWLU6I+=mQ4^C6m)b3+8 zh_ht-#QVh!N*Et-r3_ksNSd`0`d~m+-N%}P=Zn{eY*rh=+CcPHv`{DmISUdAQ7fL+ zw?;ooR4PQ=Jd55~qE(D)s4KC)F~>IzQyCxW3?g*nuPZsDtrDlS^v@$gtQpLpn;%9- z&v~rx1j3d{_4F;#GOp(qL{)`1)y>j2{(0RI3M(FG8J|&IUm5!TZ-#-SQZ+&0_~W`| zW`WANCXb`DL1Sb0U&K zV?p2bP4-*Bg`mR5gLr}%c~Oh#uLTN6+1F#oWAhrgIZR^n-c#`9jiZ*^L#LayR+m&D zd{10tu?+no|A?BM(&ZVP>&?HXTevqC1tw5Zm!`}DNv5@4E=z`9k7?(U*TV?kDrB~E znI*ul;c&-TK)2jYMFI1$p6i`3X-qgh#&`Y!io-gMVZ}`>!LQaYnDYTzg=CvbQ)OR~ zecaEn3qrt8kird<2#N7tvv$JcAMoxeL5cH>k8klauJ zG3(EsicQ~JTV6PO#OFQ`pu;C@xU?pVgitAT${txn<0mnz`vwGP)(VYq@uJx8&T93> zF7K@UBehfN#wTf;2T;&`e1eX8 zl?2;cCS}3zIBvn%sj(nq&}8NhV|+&R!^CF?aQ;W%sOqTNr&mX9Bqljo^bbJiP@Gqz z5#5G(jSW+YW3J1u69@%N7H&Iy3Ywndx#q1+bGH=c$e|!F^<0b6 zjDnjI6hRgVo=%b;|DgZH|J5C&R$lOnwFku4z2-%CmQjOIH{d8{Q*nXMoxCb=w}HI! zW5>VU<~J=sTNwkWZIg*WhD@{4rCfC`MNe6qc&V^-3cu^YbcwN0<0RT2LtN_c=)U(aZ8&foMEuh7YV))x+HF%c;A^)9R;6%{&gHSig;1=G0f06 zCc(YIOkDY`O=`fc(FKN>svT0*ea@Q=N4o$fx%fn(@0hj*%P)YK%r5PYtrooRST=J! zvA84vmKz~BKFvN?lGay?H_C~iAfWx{G-h3*XBHKx-?|uL;~5Zuw?-T5#Kce;f;4iT zSG;+HHVgx_=Q5m;kTeso)3BKBWt3rh$8Z13BF12?)WV#dt9buUm|mL%AT))V0r7Ua!U3oX2)k`fxCAG*4z8kxkR*j5kN#2$pWZCJ%_yw8T@N@#7TXpk#zb*l#Lss!dUEg3~mU zj|h4^Svti?dp_E7bAssZG^$cZGU6WaP+9?_PPFQ*9y}#iXQ~i-7|QF^;-I7X zYFt@`tJQH;AzNgcyq{QYE}?9zu8fE?3@NtdYEl1I`?P3a$D>j^!XU`Nk^Iu9rL|mg8AJMJyS(&Y zFB$cwi8rbKfg1IHLe%ONvsV{6^3q-a97sZsK)w(ZRB*Flq;LdOD)1=6q$ywz8IA(3pPipS>lT49 zs^%W!YaA^n&*bdjNZE?$)^gCcry4?I(MWerrz1vZILKr0IMA5TBvvs^&%)k*IpceF zeeL4|NPs44{)Pd!V3x5kRxqMMGsFLu?QPY*X?4=Xp>?hidp1ZpwMZ`~*oHjBW`I9l z^S+9%c3j&)7!B1dr@<-v?%p zAB{bSSn3nPX&>2Lh$L@bo>Krm?M~O{gd2vU{WF5?8|>EuJ4~EF+n2TknPuujc>#_Q zE8;uy2qG^21sFfQaeZJHm8A1ni#Rz8n!$|M?KKhkhjc5$4q2+5ddcMt|_Fld-; z9m516qjZ}W_0dqEw5LlwB;#DWJ&qfouG^Z>HZ4J- z@dF0!8=3QZZBg{kAu9Fvts>w3i$d4^49TI^1yo5RgSgElm1JNTx#?{rke>q-xUfnOQt@&=d6#dc;R$3|;COg1>yLsb{6 zYxIo`@udQ-dW0H0l_Dyh6~!8fA3cx{MDIebPGGL3Eb<1~#e z0!~v0!Og{I2mCzMG4@uhD`iao;_{LxgpP>hFjKZxf|(dNCaT*|H0*71f<1Q~jK2`6 zuPn>F;zL>xX6#6ti%(k-Q8Nxv!|!9bD7G^*p}zHIXS`T%vEuajc$f5=gBE!=i`lvs z3H~Uoo_p&<^36&kWd++*?9)CG5)8O~(uCT~1$E}GBX)l8{irfy=_`K)rz~i0dzo4x z<4$84aer`n9NHkM#pc6Zw!zn5cRqPfyg`Ql;(V?DAwgE$c3{~H0EWZDRJtyl%ChXJ z2x~34|7~6eKLJksS!ZWy#-y|XBH}p0k!{(-i3zzJ!<~N|vKgnkF!m7fL>p+8m*u$y zZhU96|7E1(1ERwI1}I6~9sc)-<=bRXr-p}2(oOA>SI%QWrPDpP=nVTALvu+Bw()i` z__XrcT3}aO*qhvbJNGywzHeESlZIv6{n;NlbB_za;^1eq3tleqMxs&@>Q827ky;7J zQD?|-`hh6VtV}U#?*c*|{(}U!%;hD2Wu^S}lfJQBg%op0 z%cgvSG$yFa_%?>M!gYXvjdsEYbEjg+lEVjgJ{_J4;4S5ABAEcU?Vu{N@RM98zEawr zMC44u>ivghUq^KenEh)@i;e73h-VMwnJcO9uX%jFXOTf20v^}>fxl{4Wj>r!Hvjo= z=;oXAO@G?;-29n~{y(~z=s#`oYLknTqTjUOGhC$Zv>X5!-oFkELA_FI%}<8C*TS<0 zI7n2f;F@!HRdwN5M69_y97l8AfU#shHQ?BWx7HiGl`GNmM?nJ?fF!lfu?(U$wNhuGCnO3KRpm zLO^NM6-A~3zgR4NZ9-F)9><_h54;m^t(H@3?0pj!nvgHYK8jmlGY|Z|STj*jY4j zT{nyad2#*j=?=&3w)>Yi>tUAs3deJtG)cm~m3aQ+>5+c3u>I-=_|Ti-mB8p2^lA6h z`7IjwbBeDfmaa*C=7dkNlrGkX5_zM*{NzFU@S=YH$nAnDfXxzd*jN-rJ&b5QrbRAf$81SRZk&bmpA_g*{&E?e=5ADd!K zX1|Vf+DQJ}@kIR!EZb;b`|;I>zH|AQw4<%LD^iZqb?7=HKve%==zWkU#4)+WBZ-CN z!vv6uk=uFg08BBmM2DyIw_mC`5JI|_SSV?v$~;u6 zYJ#2=s%-8pN&-a>fjFi&hBOA@pl7t`!~uIxE&Rj{S@#9X{tuAtpVh6#j>6GuOpF2- zaR*h$4}}H#V@h(NE;(~O-CB4KCoZ1z`3?QIzyte(0HH}K%HMw_j&J7wdp17MI1;?& z`(5E?$+5hmxB>}=Zh7H2zMOc2+dWOrn(8vzK<~X&**q_*t{o82j2E2+iR2~#QE-&IcR(^Q?`_Bz+j?_#a6|g0jMU?MXxr~B z@VJ2-CC_V`OHBgLA!=VP^Bz{q z3fYYNCoxnD;|oD(cl-@n`L=N|-m616s~RGIHhQ9=}a zFI23X7;JFJjKNgjbd*>I<1Z`C6qWj?g8FNjBg4h)PzAET{2FD68NSm>#)!~l+Kl@D zQoy}&OnUuyAzNBhcRTb$5Jtw&C#!WKm>M;3;}oQEA8w(C6F4kJV3FpT=gb%)VhC}7 zufR^rOnHxXk{?gvBXcSjc_wRz$?orTo}xUj1SZfe0$L1Y3K>3kNM2)5Ad2ZKI4(+KwVRC9zRQ3fCRDB<(vS7ZP$|hK zZ*R~#YFGjoSkj!SAoz79)gcomUS2Ifs-7uZh z;wGJ<_YL!B*Tvb2>8b~b4l?pfD|cr;xu+3HD4yX@S!PxK(jGo9WS!f! zNe7{d*ArOq805qIi62CmfJm)r*3rw?D`a{S9d00@Klt_zPrG2lA^#)*<2{)>ItKZo z+O4*S|Gi`o7L>)5k*-?YCg!8N^oF`Fl%W3IS6s0Box-yP z=tw?HshG9posSumh8VD8mb2G-Gex%>04Q)vA*O zn?}?Ybn)Y5%NP(HO4_$aaNOGJWNB>elf!rgq>E$JabpTgawTX#Z~0a%GslYwIse?{ z2?D*A_ua5F*CR}44-n(${lB2RiK`)q1EuGmHSo1PyPo!U`uuBG>0Ki|f#x+stuYA_ z=ahShpdVm2p~dPUE$IX>O#b; zSrYM!*6o;IP&TfhfK${N>6@+`PTy;NS=1!UpumgrZDL+3{z}j#nz*Li$}{T5=W=2B zLSuhE(q4V@C_S%RK3KH-`}^MIlR9PJ2r4viF^6HMXsLn@DZy?W z%K#7_A!{(2D7y&mBT6VZI8FWtT7@~BF;jC|3RLa(Ayals`l<9aOg@rLJ_nxCK!uGR>ZS1Zu5%e4n zD7e69`yDuMtB6-Pkbid^FiBO0CosiK&t}Y1u{|L2q%H|sP#nMT%Y4ul2F-EqdyBdW zodPubUsaE#G3?(h2mWrH)%&~A+qNfWSqn{x+~WSH!ev)7H92l^>^mK~*xh3C(&+4U z(_?E#nfoIgZdfyy!~AyWX|iy1G&D746!1GloFq`-FL?wQ4J{D23xtN&4(t<$hGu~Q z#zsRE3<6=Iq5c2*_+=94?D%-P;m1^!xzEy_{TK8uz*)bT`v~H_dsePAwLBsT81wE`nbs6ORoG8mDP^BuuWpV$Wzbx5$T* zFL$csIFgP=TDKithVe~-98%4aTR%;ro)X19rOKZB-uasv4Q7j$F zzgLz((HK5f1AM37hM#&SLHhdY=Ax1GE_Z<}U~Sk|>yNL%FS_^-Te1T;Xkn8R^=b97 z3PDQ)4^vC3`N347k07c)_Qy$Lt%pQex{-cNW;Aly3;6vQdy@~#;3KySkEir4+Oe+U zTPW+_<1=|Jesg@BgZ(ra4gsPl>;SZ?3YaEvfgg;h!&`rtlaBCKWtbZ$6VFRVBac>m z#QMlNywWrWeBAhlhTz?iDIOoa8p~P?Ug@zvA%ru2hoT9?P4fF2@cbZSy`USLUM_9s7k8n1%Q~2)OgIX`k zpx$(v(pGgF&PMS)OlAbB9l`tn^$EK5bsG9cVNHo`QB4!IOJX z5weYshOw`J1JcvQbROYoiwuGX+&+CSh~1b0oz1qD3ZmM~+R|i1{a8?6-D=$q5C1`~ z9MPTU#329s5AENBZjKDuIw53H;K}zxd*C~JBLPak-P~LC3+YM zG*2exiMpiqg3!Gb2ImKY$C%HZp7;Sj40ar8^Ijdi$9dPM?P9&!4L&h#S8l3ma^%j7 z&sIs4EE-`naJKq`DGvD(<+Ay5XOj>0mFNKM)$|hjQs((vI_QbpM9(zG;g6>!z$3n3 zZ390P*=q`rc;tU-3ih&deZ`{#^KrM~Bli2U;7z@rjQ^bvwP*9wb5@nEt#-~bALYdYjf1X8VwRl;WxPhHKxq7A>4D`S0 zGg#^A&Xva@4QsZ|k4bx^GSS}^G9_2`e7w`lyZvn3bl)6UP3NLx5I)ZIy+ls?6ifJp z_FgZWd-_3tS$^p0>&srLjA6dQ}ily)Y1et0!9x<~;}6!+LwRa*S5 ec3ru4iJQ*f6~&;R!+@_vd#t3PSSfE2{C@y+zfuMO literal 0 HcmV?d00001 -- Gitee