From f2a66cb39df071333547e0fd060ef059b6fe8d44 Mon Sep 17 00:00:00 2001 From: Kun Date: Sun, 26 Jul 2026 01:45:10 +0800 Subject: [PATCH] update 26.7.26 --- 480T/dify/.env.example | 296 ++++ 480T/dify/.gitignore | 3 + 480T/dify/README.md | 182 +++ 480T/dify/certbot/README.md | 76 + 480T/dify/certbot/docker-entrypoint.sh | 30 + 480T/dify/certbot/update-cert.template.txt | 19 + 480T/dify/couchbase-server/Dockerfile | 4 + 480T/dify/couchbase-server/init-cbserver.sh | 44 + 480T/dify/dify-env-sync.py | 449 ++++++ 480T/dify/dify-env-sync.sh | 465 ++++++ 480T/dify/docker-compose-template.yaml | 1264 +++++++++++++++++ 480T/dify/docker-compose.middleware.yaml | 260 ++++ 480T/dify/docker-compose.png | Bin 0 -> 174447 bytes 480T/dify/docker-compose.pytest.ports.yaml | 30 + 480T/dify/elasticsearch/docker-entrypoint.sh | 25 + 480T/dify/envs/core-services/api.env.example | 18 + .../envs/core-services/dify-agent.env.example | 36 + .../core-services/local-sandbox.env.example | 16 + .../core-services/plugin-daemon.env.example | 23 + .../envs/core-services/sandbox.env.example | 17 + .../envs/core-services/shared.env.example | 513 +++++++ 480T/dify/envs/core-services/web.env.example | 32 + .../core-services/worker-beat.env.example | 8 + .../envs/core-services/worker.env.example | 13 + 480T/dify/envs/databases/db-mysql.env.example | 9 + .../envs/databases/db-postgres.env.example | 26 + 480T/dify/envs/databases/redis.env.example | 39 + .../envs/infrastructure/certbot.env.example | 7 + .../dify/envs/infrastructure/etcd.env.example | 4 + .../milvus-standalone.env.example | 4 + .../envs/infrastructure/minio.env.example | 4 + .../envs/infrastructure/nginx.env.example | 18 + .../infrastructure/ssrf-proxy.env.example | 17 + 480T/dify/envs/middleware.env.example | 245 ++++ 480T/dify/envs/security.env.example | 41 + .../dify/envs/vectorstores/chroma.env.example | 13 + .../envs/vectorstores/couchbase.env.example | 9 + .../vectorstores/elasticsearch.env.example | 17 + 480T/dify/envs/vectorstores/iris.env.example | 17 + .../envs/vectorstores/matrixone.env.example | 9 + .../dify/envs/vectorstores/milvus.env.example | 16 + .../envs/vectorstores/myscale.env.example | 10 + .../envs/vectorstores/oceanbase.env.example | 6 + .../envs/vectorstores/opengauss.env.example | 12 + .../envs/vectorstores/opensearch.env.example | 22 + .../dify/envs/vectorstores/oracle.env.example | 13 + .../envs/vectorstores/pgvecto-rs.env.example | 9 + .../envs/vectorstores/pgvector.env.example | 8 + .../dify/envs/vectorstores/qdrant.env.example | 4 + .../dify/envs/vectorstores/seekdb.env.example | 4 + .../envs/vectorstores/vastbase.env.example | 11 + .../envs/vectorstores/weaviate.env.example | 18 + 480T/dify/generate_docker_compose | 160 +++ 480T/dify/iris/docker-entrypoint.sh | 56 + 480T/dify/iris/iris-init.script | 11 + 480T/dify/nginx/conf.d/default.conf.template | 73 + 480T/dify/nginx/docker-entrypoint.sh | 42 + 480T/dify/nginx/https.conf.template | 9 + 480T/dify/nginx/nginx.conf.template | 34 + 480T/dify/nginx/proxy.conf.template | 11 + 480T/dify/nginx/ssl/.gitkeep | 0 480T/dify/pgvector/docker-entrypoint.sh | 24 + 480T/dify/ssrf_proxy/docker-entrypoint.sh | 90 ++ 480T/dify/ssrf_proxy/squid.conf.template | 121 ++ .../dify/ssrf_proxy/test_ssrf_proxy_config.sh | 143 ++ 480T/dify/startupscripts/init.sh | 13 + 480T/dify/startupscripts/init_user.script | 10 + 480T/dify/tidb/config/pd.toml | 4 + 480T/dify/tidb/config/tiflash-learner.toml | 13 + 480T/dify/tidb/config/tiflash.toml | 19 + 480T/dify/tidb/docker-compose.yaml | 63 + .../config/users.d/custom_users_config.xml | 17 + .../volumes/oceanbase/init.d/vec_memory.sql | 1 + .../opensearch/opensearch_dashboards.yml | 222 +++ 480T/dify/volumes/sandbox/conf/config.yaml | 13 + .../volumes/sandbox/conf/config.yaml.example | 35 + 76 files changed, 5619 insertions(+) create mode 100755 480T/dify/.env.example create mode 100755 480T/dify/.gitignore create mode 100755 480T/dify/README.md create mode 100755 480T/dify/certbot/README.md create mode 100755 480T/dify/certbot/docker-entrypoint.sh create mode 100755 480T/dify/certbot/update-cert.template.txt create mode 100755 480T/dify/couchbase-server/Dockerfile create mode 100755 480T/dify/couchbase-server/init-cbserver.sh create mode 100755 480T/dify/dify-env-sync.py create mode 100755 480T/dify/dify-env-sync.sh create mode 100755 480T/dify/docker-compose-template.yaml create mode 100755 480T/dify/docker-compose.middleware.yaml create mode 100755 480T/dify/docker-compose.png create mode 100755 480T/dify/docker-compose.pytest.ports.yaml create mode 100755 480T/dify/elasticsearch/docker-entrypoint.sh create mode 100755 480T/dify/envs/core-services/api.env.example create mode 100755 480T/dify/envs/core-services/dify-agent.env.example create mode 100755 480T/dify/envs/core-services/local-sandbox.env.example create mode 100755 480T/dify/envs/core-services/plugin-daemon.env.example create mode 100755 480T/dify/envs/core-services/sandbox.env.example create mode 100755 480T/dify/envs/core-services/shared.env.example create mode 100755 480T/dify/envs/core-services/web.env.example create mode 100755 480T/dify/envs/core-services/worker-beat.env.example create mode 100755 480T/dify/envs/core-services/worker.env.example create mode 100755 480T/dify/envs/databases/db-mysql.env.example create mode 100755 480T/dify/envs/databases/db-postgres.env.example create mode 100755 480T/dify/envs/databases/redis.env.example create mode 100755 480T/dify/envs/infrastructure/certbot.env.example create mode 100755 480T/dify/envs/infrastructure/etcd.env.example create mode 100755 480T/dify/envs/infrastructure/milvus-standalone.env.example create mode 100755 480T/dify/envs/infrastructure/minio.env.example create mode 100755 480T/dify/envs/infrastructure/nginx.env.example create mode 100755 480T/dify/envs/infrastructure/ssrf-proxy.env.example create mode 100755 480T/dify/envs/middleware.env.example create mode 100755 480T/dify/envs/security.env.example create mode 100755 480T/dify/envs/vectorstores/chroma.env.example create mode 100755 480T/dify/envs/vectorstores/couchbase.env.example create mode 100755 480T/dify/envs/vectorstores/elasticsearch.env.example create mode 100755 480T/dify/envs/vectorstores/iris.env.example create mode 100755 480T/dify/envs/vectorstores/matrixone.env.example create mode 100755 480T/dify/envs/vectorstores/milvus.env.example create mode 100755 480T/dify/envs/vectorstores/myscale.env.example create mode 100755 480T/dify/envs/vectorstores/oceanbase.env.example create mode 100755 480T/dify/envs/vectorstores/opengauss.env.example create mode 100755 480T/dify/envs/vectorstores/opensearch.env.example create mode 100755 480T/dify/envs/vectorstores/oracle.env.example create mode 100755 480T/dify/envs/vectorstores/pgvecto-rs.env.example create mode 100755 480T/dify/envs/vectorstores/pgvector.env.example create mode 100755 480T/dify/envs/vectorstores/qdrant.env.example create mode 100755 480T/dify/envs/vectorstores/seekdb.env.example create mode 100755 480T/dify/envs/vectorstores/vastbase.env.example create mode 100755 480T/dify/envs/vectorstores/weaviate.env.example create mode 100755 480T/dify/generate_docker_compose create mode 100755 480T/dify/iris/docker-entrypoint.sh create mode 100755 480T/dify/iris/iris-init.script create mode 100755 480T/dify/nginx/conf.d/default.conf.template create mode 100755 480T/dify/nginx/docker-entrypoint.sh create mode 100755 480T/dify/nginx/https.conf.template create mode 100755 480T/dify/nginx/nginx.conf.template create mode 100755 480T/dify/nginx/proxy.conf.template create mode 100755 480T/dify/nginx/ssl/.gitkeep create mode 100755 480T/dify/pgvector/docker-entrypoint.sh create mode 100755 480T/dify/ssrf_proxy/docker-entrypoint.sh create mode 100755 480T/dify/ssrf_proxy/squid.conf.template create mode 100755 480T/dify/ssrf_proxy/test_ssrf_proxy_config.sh create mode 100755 480T/dify/startupscripts/init.sh create mode 100755 480T/dify/startupscripts/init_user.script create mode 100755 480T/dify/tidb/config/pd.toml create mode 100755 480T/dify/tidb/config/tiflash-learner.toml create mode 100755 480T/dify/tidb/config/tiflash.toml create mode 100755 480T/dify/tidb/docker-compose.yaml create mode 100755 480T/dify/volumes/myscale/config/users.d/custom_users_config.xml create mode 100755 480T/dify/volumes/oceanbase/init.d/vec_memory.sql create mode 100755 480T/dify/volumes/opensearch/opensearch_dashboards.yml create mode 100755 480T/dify/volumes/sandbox/conf/config.yaml create mode 100755 480T/dify/volumes/sandbox/conf/config.yaml.example diff --git a/480T/dify/.env.example b/480T/dify/.env.example new file mode 100755 index 0000000..3b3de9c --- /dev/null +++ b/480T/dify/.env.example @@ -0,0 +1,296 @@ +# ------------------------------------------------------------------ +# Essential defaults for Docker Compose deployments. +# Only include variables required for services to start. +# Do not add optional variables to this file. +# +# For a default deployment, copy this file to .env and run: +# docker compose up -d +# +# Optional and provider-specific variables live under docker/envs/. +# Copy an optional *.env.example file beside itself without the +# .example suffix when you need those advanced settings. +# Values in docker/.env take precedence over docker/envs/*.env files. +# ------------------------------------------------------------------ + +# Core service URLs +CONSOLE_API_URL= +SERVER_CONSOLE_API_URL=http://api:5001 +CONSOLE_WEB_URL= +SERVICE_API_URL= +TRIGGER_URL=http://localhost +APP_API_URL= +APP_WEB_URL= +FILES_URL= +INTERNAL_FILES_URL= +ENDPOINT_URL_TEMPLATE=http://localhost/e/{hook_id} +NEXT_PUBLIC_SOCKET_URL=ws://localhost + +# Runtime and security +LANG=C.UTF-8 +LC_ALL=C.UTF-8 +PYTHONIOENCODING=utf-8 +UV_CACHE_DIR=/tmp/.uv-cache +# Leave empty to auto-generate a persistent key in the storage directory. +SECRET_KEY= +INIT_PASSWORD= +DEPLOY_ENV=PRODUCTION +CHECK_UPDATE_URL=https://updates.dify.ai +OPENAI_API_BASE=https://api.openai.com/v1 +MIGRATION_ENABLED=true +FILES_ACCESS_TIMEOUT=300 +# Remove `collaboration` from COMPOSE_PROFILES to stop the dedicated websocket service. +ENABLE_COLLABORATION_MODE=true + +# Learn app feature toggle +ENABLE_LEARN_APP=true + +# Logging and server workers +LOG_LEVEL=INFO +LOG_OUTPUT_FORMAT=text +LOG_FILE=/app/logs/server.log +LOG_FILE_MAX_SIZE=20 +LOG_FILE_BACKUP_COUNT=5 +LOG_DATEFORMAT=%Y-%m-%d %H:%M:%S +LOG_TZ=UTC +DEBUG=false +FLASK_DEBUG=false +ENABLE_REQUEST_LOGGING=False +DIFY_BIND_ADDRESS=0.0.0.0 +DIFY_PORT=5001 +SERVER_WORKER_AMOUNT=1 +SERVER_WORKER_CLASS=gevent +SERVER_WORKER_CONNECTIONS=10 +API_WEBSOCKET_WORKER_AMOUNT=1 +API_WEBSOCKET_WORKER_CLASS=geventwebsocket.gunicorn.workers.GeventWebSocketWorker +API_WEBSOCKET_WORKER_CONNECTIONS=1000 +API_WEBSOCKET_GUNICORN_TIMEOUT=360 +GUNICORN_TIMEOUT=360 +CELERY_WORKER_CLASS= +CELERY_WORKER_AMOUNT=4 +CELERY_AUTO_SCALE=false +CELERY_MAX_WORKERS= +CELERY_MIN_WORKERS= +COMPOSE_WORKER_HEALTHCHECK_DISABLED=true +COMPOSE_WORKER_HEALTHCHECK_INTERVAL=30s +COMPOSE_WORKER_HEALTHCHECK_TIMEOUT=30s + +# Database +DB_TYPE=postgresql +DB_USERNAME=postgres +DB_PASSWORD=difyai123456 +DB_HOST=db_postgres +DB_PORT=5432 +DB_DATABASE=dify +SQLALCHEMY_POOL_SIZE=30 +SQLALCHEMY_MAX_OVERFLOW=10 +SQLALCHEMY_POOL_RECYCLE=3600 +SQLALCHEMY_ECHO=false +SQLALCHEMY_POOL_PRE_PING=false +SQLALCHEMY_POOL_USE_LIFO=false +SQLALCHEMY_POOL_TIMEOUT=30 +SQLALCHEMY_POOL_RESET_ON_RETURN=rollback +PGDATA=/var/lib/postgresql/data/pgdata +POSTGRES_MAX_CONNECTIONS=200 +POSTGRES_SHARED_BUFFERS=128MB +POSTGRES_WORK_MEM=4MB +POSTGRES_MAINTENANCE_WORK_MEM=64MB +POSTGRES_EFFECTIVE_CACHE_SIZE=4096MB +POSTGRES_STATEMENT_TIMEOUT=0 +POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT=0 + +# Redis and Celery +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_USERNAME= +REDIS_PASSWORD=difyai123456 +REDIS_USE_SSL=false +REDIS_SSL_CERT_REQS=CERT_NONE +REDIS_SSL_CA_CERTS= +REDIS_SSL_CERTFILE= +REDIS_SSL_KEYFILE= +REDIS_DB=0 +REDIS_KEY_PREFIX= +REDIS_MAX_CONNECTIONS= +REDIS_RETRY_RETRIES=3 +REDIS_RETRY_BACKOFF_BASE=1.0 +REDIS_RETRY_BACKOFF_CAP=10.0 +REDIS_SOCKET_TIMEOUT=5.0 +REDIS_SOCKET_CONNECT_TIMEOUT=5.0 +REDIS_HEALTH_CHECK_INTERVAL=30 +REDIS_KEEPALIVE=true +REDIS_KEEPALIVE_IDLE=30 +REDIS_KEEPALIVE_INTERVAL=10 +REDIS_KEEPALIVE_COUNT=10 +CELERY_BROKER_URL=redis://:difyai123456@redis:6379/1 +CELERY_BACKEND=redis +BROKER_USE_SSL=false +CELERY_TASK_ANNOTATIONS=null +EVENT_BUS_REDIS_URL= +EVENT_BUS_REDIS_CHANNEL_TYPE=pubsub +EVENT_BUS_REDIS_USE_CLUSTERS=false + +# Web and app limits +WEB_API_CORS_ALLOW_ORIGINS=* +CONSOLE_CORS_ALLOW_ORIGINS=* +COOKIE_DOMAIN= +NEXT_PUBLIC_COOKIE_DOMAIN= +NEXT_PUBLIC_BATCH_CONCURRENCY=5 +API_SENTRY_DSN= +API_SENTRY_TRACES_SAMPLE_RATE=1.0 +API_SENTRY_PROFILES_SAMPLE_RATE=1.0 +WEB_SENTRY_DSN= +AMPLITUDE_API_KEY= +COOKIEYES_SITE_KEY= +TEXT_GENERATION_TIMEOUT_MS=60000 +WORKFLOW_GENERATION_TIMEOUT_MS=180000 +CSP_WHITELIST= +ALLOW_EMBED=false +ALLOW_UNSAFE_DATA_SCHEME=false +TOP_K_MAX_VALUE=10 +INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000 +LOOP_NODE_MAX_COUNT=100 +MAX_TOOLS_NUM=10 +MAX_PARALLEL_LIMIT=10 +MAX_ITERATIONS_NUM=99 +MAX_TREE_DEPTH=50 +ENABLE_WEBSITE_JINAREADER=true +ENABLE_WEBSITE_FIRECRAWL=true +ENABLE_WEBSITE_WATERCRAWL=true +NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX=false +# Enable preview features still in development (currently the /create and +# /refine slash commands in the "Go to Anything" command palette). +NEXT_PUBLIC_ENABLE_FEATURE_PREVIEW=true +NEXT_PUBLIC_ENABLE_AGENT_V2=true +EXPERIMENTAL_ENABLE_VINEXT=false + +# Storage and default vector store +STORAGE_TYPE=opendal +OPENDAL_SCHEME=fs +OPENDAL_FS_ROOT=storage +VECTOR_STORE=weaviate +VECTOR_INDEX_NAME_PREFIX=Vector_index +WEAVIATE_ENDPOINT=http://weaviate:8080 +WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih +WEAVIATE_GRPC_ENDPOINT=grpc://weaviate:50051 +WEAVIATE_TOKENIZATION=word +WEAVIATE_PERSISTENCE_DATA_PATH=/var/lib/weaviate +WEAVIATE_QUERY_DEFAULTS_LIMIT=25 +WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true +WEAVIATE_DEFAULT_VECTORIZER_MODULE=none +WEAVIATE_CLUSTER_HOSTNAME=node1 +WEAVIATE_AUTHENTICATION_APIKEY_ENABLED=true +WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih +WEAVIATE_AUTHENTICATION_APIKEY_USERS=hello@dify.ai +WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED=true +WEAVIATE_AUTHORIZATION_ADMINLIST_USERS=hello@dify.ai +WEAVIATE_DISABLE_TELEMETRY=false +WEAVIATE_ENABLE_TOKENIZER_GSE=false +WEAVIATE_ENABLE_TOKENIZER_KAGOME_JA=false +WEAVIATE_ENABLE_TOKENIZER_KAGOME_KR=false + +# Sandbox and SSRF proxy +CODE_EXECUTION_ENDPOINT=http://sandbox:8194 +CODE_EXECUTION_API_KEY=dify-sandbox +CODE_EXECUTION_SSL_VERIFY=True +CODE_EXECUTION_POOL_MAX_CONNECTIONS=100 +CODE_EXECUTION_POOL_MAX_KEEPALIVE_CONNECTIONS=20 +CODE_EXECUTION_POOL_KEEPALIVE_EXPIRY=5.0 +CODE_EXECUTION_CONNECT_TIMEOUT=10 +CODE_EXECUTION_READ_TIMEOUT=60 +CODE_EXECUTION_WRITE_TIMEOUT=10 +SANDBOX_API_KEY=dify-sandbox +SANDBOX_GIN_MODE=release +SANDBOX_WORKER_TIMEOUT=15 +SANDBOX_ENABLE_NETWORK=true +SANDBOX_HTTP_PROXY=http://ssrf_proxy:3128 +SANDBOX_HTTPS_PROXY=http://ssrf_proxy:3128 +SANDBOX_PORT=8194 +PIP_MIRROR_URL= +SSRF_PROXY_HTTP_URL=http://ssrf_proxy:3128 +SSRF_PROXY_HTTPS_URL=http://ssrf_proxy:3128 +SSRF_HTTP_PORT=3128 +SSRF_COREDUMP_DIR=/var/spool/squid +SSRF_DEFAULT_TIME_OUT=5 +SSRF_DEFAULT_CONNECT_TIME_OUT=5 +SSRF_DEFAULT_READ_TIME_OUT=5 +SSRF_DEFAULT_WRITE_TIME_OUT=5 +SSRF_POOL_MAX_CONNECTIONS=100 +SSRF_POOL_MAX_KEEPALIVE_CONNECTIONS=20 +SSRF_POOL_KEEPALIVE_EXPIRY=5.0 + +# Plugin daemon +DB_PLUGIN_DATABASE=dify_plugin +EXPOSE_PLUGIN_DAEMON_PORT=5002 +PLUGIN_DAEMON_PORT=5002 +PLUGIN_DAEMON_KEY=lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi +PLUGIN_DAEMON_URL=http://plugin_daemon:5002 +PLUGIN_MAX_PACKAGE_SIZE=52428800 +PLUGIN_MODEL_SCHEMA_CACHE_TTL=3600 +PLUGIN_PPROF_ENABLED=false +PLUGIN_DEBUGGING_HOST=0.0.0.0 +PLUGIN_DEBUGGING_PORT=5003 +EXPOSE_PLUGIN_DEBUGGING_HOST=localhost +EXPOSE_PLUGIN_DEBUGGING_PORT=5003 +PLUGIN_DIFY_INNER_API_KEY=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 +PLUGIN_DIFY_INNER_API_URL=http://api:5001 +FORCE_VERIFYING_SIGNATURE=true +PLUGIN_STDIO_BUFFER_SIZE=1024 +PLUGIN_STDIO_MAX_BUFFER_SIZE=5242880 +PLUGIN_PYTHON_ENV_INIT_TIMEOUT=120 +PLUGIN_MAX_EXECUTION_TIMEOUT=600 +PLUGIN_STORAGE_TYPE=local +PLUGIN_STORAGE_LOCAL_ROOT=/app/storage +PLUGIN_WORKING_PATH=/app/storage/cwd +PLUGIN_INSTALLED_PATH=plugin +PLUGIN_PACKAGE_CACHE_PATH=plugin_packages +PLUGIN_MEDIA_CACHE_PATH=assets +PLUGIN_STORAGE_OSS_BUCKET= +PLUGIN_SENTRY_ENABLED=false +PLUGIN_SENTRY_DSN= +MARKETPLACE_ENABLED=true +MARKETPLACE_API_URL=https://marketplace.dify.ai +MARKETPLACE_URL= + +# Dify Agent backend +AGENT_BACKEND_BASE_URL=http://agent_backend:5050 +AGENT_BACKEND_STREAM_READ_TIMEOUT_SECONDS=30 +AGENT_BACKEND_STREAM_MAX_RECONNECTS=3 +AGENT_BACKEND_RUN_TIMEOUT_SECONDS=1200 +# Leave empty to derive from REDIS_PASSWORD. +DIFY_AGENT_REDIS_URL= +DIFY_AGENT_REDIS_PREFIX=dify-agent +DIFY_AGENT_SHUTDOWN_GRACE_SECONDS=30 +DIFY_AGENT_RUN_RETENTION_SECONDS=259200 +# Leave empty to derive from PLUGIN_DAEMON_URL and PLUGIN_DAEMON_KEY. +DIFY_AGENT_PLUGIN_DAEMON_URL= +DIFY_AGENT_PLUGIN_DAEMON_API_KEY= +# Leave empty to derive from PLUGIN_DIFY_INNER_API_URL and PLUGIN_DIFY_INNER_API_KEY. +# DIFY_AGENT_INNER_API_KEY must match API/worker INNER_API_KEY_FOR_PLUGIN, not INNER_API_KEY. +DIFY_AGENT_INNER_API_URL= +DIFY_AGENT_INNER_API_KEY= +DIFY_AGENT_SHELLCTL_ENTRYPOINT=http://local_sandbox:5004 +DIFY_AGENT_SHELLCTL_AUTH_TOKEN= +DIFY_AGENT_STUB_API_BASE_URL=http://agent_backend:5050/agent-stub +# This is security-sensitive: it derives the JWE encryption key for Agent Stub bearer tokens. +# Replace this development default in production. +# Generate one with: python -c 'import secrets; print(secrets.token_urlsafe(32))' +DIFY_AGENT_SERVER_SECRET_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY + +# Nginx and Docker Compose +NGINX_SERVER_NAME=_ +NGINX_HTTPS_ENABLED=false +NGINX_PORT=80 +NGINX_SSL_PORT=443 +NGINX_SSL_CERT_FILENAME=dify.crt +NGINX_SSL_CERT_KEY_FILENAME=dify.key +NGINX_SSL_PROTOCOLS=TLSv1.2 TLSv1.3 +NGINX_WORKER_PROCESSES=auto +NGINX_CLIENT_MAX_BODY_SIZE=100M +NGINX_KEEPALIVE_TIMEOUT=65 +NGINX_PROXY_READ_TIMEOUT=3600s +NGINX_PROXY_SEND_TIMEOUT=3600s +NGINX_ENABLE_CERTBOT_CHALLENGE=false +NGINX_SOCKET_IO_UPSTREAM=api_websocket:5001 +EXPOSE_NGINX_PORT=80 +EXPOSE_NGINX_SSL_PORT=443 +COMPOSE_PROFILES=${VECTOR_STORE:-weaviate},${DB_TYPE:-postgresql},collaboration diff --git a/480T/dify/.gitignore b/480T/dify/.gitignore new file mode 100755 index 0000000..c3a47ad --- /dev/null +++ b/480T/dify/.gitignore @@ -0,0 +1,3 @@ +# Ignore actual .env files (keep only .env.example files in git) +*.env +!*.env.example diff --git a/480T/dify/README.md b/480T/dify/README.md new file mode 100755 index 0000000..0dedf71 --- /dev/null +++ b/480T/dify/README.md @@ -0,0 +1,182 @@ +## README for docker Deployment + +Welcome to the new `docker` directory for deploying Dify using Docker Compose. This README outlines the updates, deployment instructions, and migration details for existing users. + +### What's Updated + +- **Certbot Container**: `docker-compose.yaml` now contains `certbot` for managing SSL certificates. This container automatically renews certificates and ensures secure HTTPS connections.\ + For more information, refer to `docker/certbot/README.md`. + +- **Persistent Environment Variables**: Essential startup defaults are provided in `.env.example`, while local values are stored in `.env`, ensuring that your configurations persist across deployments. + + > What is `.env`?

+ > The `.env` file is the local startup file. Copy it from `.env.example` for a default deployment. Optional advanced settings live in `envs/*.env.example` files. + +- **Unified Vector Database Services**: All vector database services are now managed from a single Docker Compose file `docker-compose.yaml`. You can switch between different vector databases by setting the `VECTOR_STORE` environment variable in your `.env` file. + +### How to Deploy Dify with `docker-compose.yaml` + +1. **Prerequisites**: Ensure Docker and Docker Compose v2.24.0 or later are installed on your system. +2. **Environment Setup**: + - Navigate to the `docker` directory. + - Copy `.env.example` to `.env`. + - Customize `.env` when you need to change essential startup defaults. Copy optional files from `envs/` without the `.example` suffix when you need advanced settings. + - **Optional (for advanced deployments)**: + If you maintain a full `.env` file copied from `.env.example`, you may use the environment synchronization tool to keep it aligned with the latest `.env.example` updates while preserving your custom settings. + See the [Environment Variables Synchronization](#environment-variables-synchronization) section below. +3. **Running the Services**: + - Execute `docker compose up -d` from the `docker` directory to start the services. + - To specify a vector database, set the `VECTOR_STORE` variable in your `.env` file to your desired vector database service, such as `milvus`, `weaviate`, or `opensearch`. See `envs/vectorstores/` for the full list of supported options. + ```bash + cp .env.example .env + docker compose up -d + ``` + +4. **SSL Certificate Setup**: + - Refer to `docker/certbot/README.md` to set up SSL certificates using Certbot. +5. **OpenTelemetry Collector Setup**: + - Copy `envs/core-services/shared.env.example` to `envs/core-services/shared.env`. + - Set `ENABLE_OTEL=true` and configure `OTLP_BASE_ENDPOINT`. Tune the other `OTEL_*` knobs in the same file if needed. + +### How to Deploy Middleware for Developing Dify + +1. **Middleware Setup**: + - Use the `docker-compose.middleware.yaml` for setting up essential middleware services like databases and caches. + - Navigate to the `docker` directory. + - Ensure the `middleware.env` file is created by running `cp envs/middleware.env.example middleware.env` (refer to the `envs/middleware.env.example` file). +2. **Running Middleware Services**: + - Navigate to the `docker` directory. + - Execute `docker compose --env-file middleware.env -f docker-compose.middleware.yaml -p dify up -d` to start PostgreSQL/MySQL (per `DB_TYPE`) plus the bundled Weaviate instance. + +> Compose automatically loads `COMPOSE_PROFILES=${DB_TYPE:-postgresql},weaviate` from `middleware.env`, so no extra `--profile` flags are needed. Adjust variables in `middleware.env` if you want a different combination of services. + +### Migration for Existing Users + +For users migrating from the `docker-legacy` setup: + +1. **Review Changes**: Familiarize yourself with the new `.env` configuration and Docker Compose setup. +2. **Transfer Customizations**: + - If you have customized configurations such as `docker-compose.yaml`, `ssrf_proxy/squid.conf`, or `nginx/conf.d/default.conf`, you will need to reflect these changes in the `.env` file you create. +3. **Data Migration**: + - Ensure that data from services like databases and caches is backed up and migrated appropriately to the new structure if necessary. + +### Overview of `.env`, `.env.example`, and `envs/` + +- `.env.example` contains the essential default configuration for Docker Compose deployments. +- `.env` contains local startup values copied from `.env.example` and any local changes. +- `envs/*.env.example` files contain optional advanced configuration grouped by theme. + +Keep the root `.env.example` limited to variables required to start the default Docker Compose deployment. +Do not add optional, advanced, provider-specific, or service-specific variables there; place them in the appropriate `envs/*.env.example` file instead. + +Docker Compose reads `envs/*.env` files when present, then reads `.env` last so values in `.env` take precedence. + +#### Key Modules and Customization + +- **Vector Database Services**: Depending on the type of vector database used (`VECTOR_STORE`), users can set specific endpoints, ports, and authentication details. +- **Storage Services**: Depending on the storage type (`STORAGE_TYPE`), users can configure specific settings for S3, Azure Blob, Google Storage, etc. +- **API and Web Services**: Users can define URLs and other settings that affect how the API and web frontend operate. + +#### Other notable variables + +The root `.env.example` file contains the essential startup settings. Optional and provider-specific settings are grouped in `envs/*.env.example` files. Here are some of the key sections and variables: + +1. **Common Variables**: + + - `CONSOLE_API_URL`, `CONSOLE_WEB_URL`, `SERVICE_API_URL`, `APP_API_URL`, `APP_WEB_URL`: public URLs for the API and frontend services. + - `SERVER_CONSOLE_API_URL`: internal API origin used by web server-side requests and, when `INTERNAL_FILES_URL` is unset, as the default fallback for API-side internal file URL generation. Keep the default `http://api:5001` for standard Docker Compose deployments, and only change it when services must reach the API through a different internal address. + - `FILES_URL`, `INTERNAL_FILES_URL`: Public and internal base URLs for file downloads and previews. + - `ENDPOINT_URL_TEMPLATE`, `NEXT_PUBLIC_SOCKET_URL`, `TRIGGER_URL`: Additional service URLs. + + See `.env.example` for the full list. + +2. **Server Configuration**: + + - `LOG_LEVEL`, `DEBUG`, `FLASK_DEBUG`: Logging and debug settings. + - `SECRET_KEY`: A key for signing sessions, JWTs, and file URLs. Leave it empty to let Dify generate a persistent key in the storage directory, or set a unique value yourself. + +3. **Database Configuration**: + + - `DB_USERNAME`, `DB_PASSWORD`, `DB_HOST`, `DB_PORT`, `DB_DATABASE`: PostgreSQL database credentials and connection details. + +4. **Redis Configuration**: + + - `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD`: Redis server connection settings. + - `REDIS_KEY_PREFIX`: Optional global namespace prefix for Redis keys, topics, streams, and Celery Redis transport artifacts. + +5. **Celery Configuration**: + + - `CELERY_BROKER_URL`: Configuration for Celery message broker. + +6. **Storage Configuration**: + + - `STORAGE_TYPE`, `OPENDAL_SCHEME`, `OPENDAL_FS_ROOT`: Default local file storage settings. Optional storage backends are configured from the files under `envs/`. + +7. **Vector Database Configuration**: + + - `VECTOR_STORE`: Type of vector database (e.g., `weaviate`, `milvus`). See `envs/vectorstores/` for the full list of supported options. + - Specific settings for each vector store like `WEAVIATE_ENDPOINT`, `MILVUS_URI`. + +8. **CORS Configuration**: + + - `WEB_API_CORS_ALLOW_ORIGINS`, `CONSOLE_CORS_ALLOW_ORIGINS`: Settings for cross-origin resource sharing. + +9. **OpenTelemetry Configuration**: + + - `ENABLE_OTEL`: Enable OpenTelemetry collector in api. + - `OTLP_BASE_ENDPOINT`: Endpoint for your OTLP exporter. + +10. **Other Service-Specific Environment Variables**: + + - Each service like `nginx`, `redis`, `db`, and vector databases have specific environment variables that are directly referenced in the `docker-compose.yaml`. + +### Environment Variables Synchronization + +When upgrading Dify or pulling the latest changes, new environment variables may be introduced in `.env.example` only when they are required for startup, +or in the optional files under `envs/` for advanced, provider-specific, and service-specific settings. + +If you use the default workflow, review `.env.example` and keep your `.env` aligned with essential startup values. + +If you maintain a customized `.env` file copied from `.env.example`, an optional environment variables synchronization tool is provided. + +> This tool performs a **one-way synchronization** from `.env.example` to `.env`. +> Existing values in `.env` are never overwritten automatically. + +#### `dify-env-sync.sh` (Optional) + +This script compares your current `.env` file with the latest `.env.example` template and helps safely apply new or updated environment variables. + +**What it does** + +- Creates a backup of the current `.env` file before making any changes +- Synchronizes newly added environment variables from `.env.example` +- Preserves all existing custom values in `.env` +- Displays differences and variables removed from `.env.example` for review + +**Backup behavior** + +Before synchronization, the current `.env` file is saved to the `env-backup/` directory with a timestamped filename +(e.g. `env-backup/.env.backup_20231218_143022`). + +**When to use** + +- After upgrading Dify to a newer version with a full `.env` file +- When `.env.example` has been updated with new environment variables +- When managing a large or heavily customized `.env` file copied from `.env.example` + +**Usage** + +```bash +# Grant execution permission (first time only) +chmod +x dify-env-sync.sh + +# Run the synchronization +./dify-env-sync.sh +``` + +### Additional Information + +- **Continuous Improvement Phase**: We are actively seeking feedback from the community to refine and enhance the deployment process. As more users adopt this new method, we will continue to make improvements based on your experiences and suggestions. +- **Support**: For detailed configuration options and environment variable settings, refer to the `.env.example` file and the Docker Compose configuration files in the `docker` directory. + +This README aims to guide you through the deployment process using the new Docker Compose setup. For any issues or further assistance, please refer to the official documentation or contact support. diff --git a/480T/dify/certbot/README.md b/480T/dify/certbot/README.md new file mode 100755 index 0000000..62b1eee --- /dev/null +++ b/480T/dify/certbot/README.md @@ -0,0 +1,76 @@ +# Launching new servers with SSL certificates + +## Short description + +docker compose certbot configurations with Backward compatibility (without certbot container).\ +Use `docker compose --profile certbot up` to use this features. + +## The simplest way for launching new servers with SSL certificates + +1. Get letsencrypt certs\ + set `.env` values + ```properties + NGINX_SSL_CERT_FILENAME=fullchain.pem + NGINX_SSL_CERT_KEY_FILENAME=privkey.pem + NGINX_ENABLE_CERTBOT_CHALLENGE=true + CERTBOT_DOMAIN=your_domain.com + CERTBOT_EMAIL=example@your_domain.com + ``` + execute command: + ```shell + docker network prune + docker compose --profile certbot up --force-recreate -d + ``` + then after the containers launched: + ```shell + docker compose exec -it certbot /bin/sh /update-cert.sh + ``` +1. Edit `.env` file and `docker compose --profile certbot up` again.\ + set `.env` value additionally + ```properties + NGINX_HTTPS_ENABLED=true + ``` + execute command: + ```shell + docker compose --profile certbot up -d --no-deps --force-recreate nginx + ``` + Then you can access your serve with HTTPS.\ + [https://your_domain.com](https://your_domain.com) + +## SSL certificates renewal + +For SSL certificates renewal, execute commands below: + +```shell +docker compose exec -it certbot /bin/sh /update-cert.sh +docker compose exec nginx nginx -s reload +``` + +## Options for certbot + +`CERTBOT_OPTIONS` key might be helpful for testing. i.e., + +```properties +CERTBOT_OPTIONS=--dry-run +``` + +To apply changes to `CERTBOT_OPTIONS`, regenerate the certbot container before updating the certificates. + +```shell +docker compose --profile certbot up -d --no-deps --force-recreate certbot +docker compose exec -it certbot /bin/sh /update-cert.sh +``` + +Then, reload the nginx container if necessary. + +```shell +docker compose exec nginx nginx -s reload +``` + +## For legacy servers + +To use cert files dir `nginx/ssl` as before, simply launch containers WITHOUT `--profile certbot` option. + +```shell +docker compose up -d +``` diff --git a/480T/dify/certbot/docker-entrypoint.sh b/480T/dify/certbot/docker-entrypoint.sh new file mode 100755 index 0000000..a70ecd8 --- /dev/null +++ b/480T/dify/certbot/docker-entrypoint.sh @@ -0,0 +1,30 @@ +#!/bin/sh +set -e + +printf '%s\n' "Docker entrypoint script is running" + +printf '%s\n' "\nChecking specific environment variables:" +printf '%s\n' "CERTBOT_EMAIL: ${CERTBOT_EMAIL:-Not set}" +printf '%s\n' "CERTBOT_DOMAIN: ${CERTBOT_DOMAIN:-Not set}" +printf '%s\n' "CERTBOT_OPTIONS: ${CERTBOT_OPTIONS:-Not set}" + +printf '%s\n' "\nChecking mounted directories:" +for dir in "/etc/letsencrypt" "/var/www/html" "/var/log/letsencrypt"; do + if [ -d "$dir" ]; then + printf '%s\n' "$dir exists. Contents:" + ls -la "$dir" + else + printf '%s\n' "$dir does not exist." + fi +done + +printf '%s\n' "\nGenerating update-cert.sh from template" +sed -e "s|\${CERTBOT_EMAIL}|$CERTBOT_EMAIL|g" \ + -e "s|\${CERTBOT_DOMAIN}|$CERTBOT_DOMAIN|g" \ + -e "s|\${CERTBOT_OPTIONS}|$CERTBOT_OPTIONS|g" \ + /update-cert.template.txt > /update-cert.sh + +chmod +x /update-cert.sh + +printf '%s\n' "\nExecuting command:" "$@" +exec "$@" diff --git a/480T/dify/certbot/update-cert.template.txt b/480T/dify/certbot/update-cert.template.txt new file mode 100755 index 0000000..16786a1 --- /dev/null +++ b/480T/dify/certbot/update-cert.template.txt @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +DOMAIN="${CERTBOT_DOMAIN}" +EMAIL="${CERTBOT_EMAIL}" +OPTIONS="${CERTBOT_OPTIONS}" +CERT_NAME="${DOMAIN}" # 証明書名をドメイン名と同じにする + +# Check if the certificate already exists +if [ -f "/etc/letsencrypt/renewal/${CERT_NAME}.conf" ]; then + echo "Certificate exists. Attempting to renew..." + certbot renew --noninteractive --cert-name ${CERT_NAME} --webroot --webroot-path=/var/www/html --email ${EMAIL} --agree-tos --no-eff-email ${OPTIONS} +else + echo "Certificate does not exist. Obtaining a new certificate..." + certbot certonly --noninteractive --webroot --webroot-path=/var/www/html --email ${EMAIL} --agree-tos --no-eff-email -d ${DOMAIN} ${OPTIONS} +fi +echo "Certificate operation successful" +# Note: Nginx reload should be handled outside this container +echo "Please ensure to reload Nginx to apply any certificate changes." diff --git a/480T/dify/couchbase-server/Dockerfile b/480T/dify/couchbase-server/Dockerfile new file mode 100755 index 0000000..23e487e --- /dev/null +++ b/480T/dify/couchbase-server/Dockerfile @@ -0,0 +1,4 @@ +FROM couchbase/server:latest AS stage_base +# FROM couchbase:latest AS stage_base +COPY init-cbserver.sh /opt/couchbase/init/ +RUN chmod +x /opt/couchbase/init/init-cbserver.sh diff --git a/480T/dify/couchbase-server/init-cbserver.sh b/480T/dify/couchbase-server/init-cbserver.sh new file mode 100755 index 0000000..e19a650 --- /dev/null +++ b/480T/dify/couchbase-server/init-cbserver.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# used to start couchbase server - can't get around this as docker compose only allows you to start one command - so we have to start couchbase like the standard couchbase Dockerfile would +# https://github.com/couchbase/docker/blob/master/enterprise/couchbase-server/7.2.0/Dockerfile#L88 + +/entrypoint.sh couchbase-server & + +# track if setup is complete so we don't try to setup again +FILE=/opt/couchbase/init/setupComplete.txt + +if ! [ -f "$FILE" ]; then + # used to automatically create the cluster based on environment variables + # https://docs.couchbase.com/server/current/cli/cbcli/couchbase-cli-cluster-init.html + + echo $COUCHBASE_ADMINISTRATOR_USERNAME ":" $COUCHBASE_ADMINISTRATOR_PASSWORD + + sleep 20s + /opt/couchbase/bin/couchbase-cli cluster-init -c 127.0.0.1 \ + --cluster-username $COUCHBASE_ADMINISTRATOR_USERNAME \ + --cluster-password $COUCHBASE_ADMINISTRATOR_PASSWORD \ + --services data,index,query,fts \ + --cluster-ramsize $COUCHBASE_RAM_SIZE \ + --cluster-index-ramsize $COUCHBASE_INDEX_RAM_SIZE \ + --cluster-eventing-ramsize $COUCHBASE_EVENTING_RAM_SIZE \ + --cluster-fts-ramsize $COUCHBASE_FTS_RAM_SIZE \ + --index-storage-setting default + + sleep 2s + + # used to auto create the bucket based on environment variables + # https://docs.couchbase.com/server/current/cli/cbcli/couchbase-cli-bucket-create.html + + /opt/couchbase/bin/couchbase-cli bucket-create -c localhost:8091 \ + --username $COUCHBASE_ADMINISTRATOR_USERNAME \ + --password $COUCHBASE_ADMINISTRATOR_PASSWORD \ + --bucket $COUCHBASE_BUCKET \ + --bucket-ramsize $COUCHBASE_BUCKET_RAMSIZE \ + --bucket-type couchbase + + # create file so we know that the cluster is setup and don't run the setup again + touch $FILE +fi + # docker compose will stop the container from running unless we do this + # known issue and workaround + tail -f /dev/null diff --git a/480T/dify/dify-env-sync.py b/480T/dify/dify-env-sync.py new file mode 100755 index 0000000..afa39d8 --- /dev/null +++ b/480T/dify/dify-env-sync.py @@ -0,0 +1,449 @@ +#!/usr/bin/env python3 + +# ================================================================ +# Dify Environment Variables Synchronization Script +# +# Features: +# - Synchronize latest settings from .env.example to .env +# - Preserve custom settings in existing .env +# - Add new environment variables +# - Detect removed environment variables +# - Create backup files +# ================================================================ + +import argparse +import re +import shutil +import sys +from datetime import datetime +from pathlib import Path + +# ANSI color codes +RED = "\033[0;31m" +GREEN = "\033[0;32m" +YELLOW = "\033[1;33m" +BLUE = "\033[0;34m" +NC = "\033[0m" # No Color + + +def supports_color() -> bool: + """Return True if the terminal supports ANSI color codes.""" + return hasattr(sys.stdout, "isatty") and sys.stdout.isatty() + + +def log_info(message: str) -> None: + """Print an informational message in blue.""" + if supports_color(): + print(f"{BLUE}[INFO]{NC} {message}") + else: + print(f"[INFO] {message}") + + +def log_success(message: str) -> None: + """Print a success message in green.""" + if supports_color(): + print(f"{GREEN}[SUCCESS]{NC} {message}") + else: + print(f"[SUCCESS] {message}") + + +def log_warning(message: str) -> None: + """Print a warning message in yellow to stderr.""" + if supports_color(): + print(f"{YELLOW}[WARNING]{NC} {message}", file=sys.stderr) + else: + print(f"[WARNING] {message}", file=sys.stderr) + + +def log_error(message: str) -> None: + """Print an error message in red to stderr.""" + if supports_color(): + print(f"{RED}[ERROR]{NC} {message}", file=sys.stderr) + else: + print(f"[ERROR] {message}", file=sys.stderr) + + +def parse_env_file(path: Path) -> dict[str, str]: + """Parse an .env-style file and return a mapping of key to raw value. + + Lines that are blank or start with '#' (after optional whitespace) are + skipped. Only lines containing '=' are considered variable definitions. + + Args: + path: Path to the .env file to parse. + + Returns: + Ordered dict mapping variable name to its value string. + """ + variables: dict[str, str] = {} + with path.open(encoding="utf-8") as fh: + for line in fh: + line = line.rstrip("\n") + # Skip blank lines and comment lines + stripped = line.strip() + if not stripped or stripped.startswith("#"): + continue + if "=" not in line: + continue + key, _, value = line.partition("=") + key = key.strip() + if key: + variables[key] = value.strip() + return variables + + +def check_files(work_dir: Path) -> None: + """Verify required files exist; create .env from .env.example if absent. + + Args: + work_dir: Directory that must contain .env.example (and optionally .env). + + Raises: + SystemExit: If .env.example does not exist. + """ + log_info("Checking required files...") + + example_file = work_dir / ".env.example" + env_file = work_dir / ".env" + + if not example_file.exists(): + log_error(".env.example file not found") + sys.exit(1) + + if not env_file.exists(): + log_warning(".env file does not exist. Creating from .env.example.") + shutil.copy2(example_file, env_file) + log_success(".env file created") + + log_success("Required files verified") + + +def create_backup(work_dir: Path) -> None: + """Create a timestamped backup of the current .env file. + + Backups are placed in ``/env-backup/`` with the filename + ``.env.backup_``. + + Args: + work_dir: Directory containing the .env file to back up. + """ + env_file = work_dir / ".env" + if not env_file.exists(): + return + + backup_dir = work_dir / "env-backup" + if not backup_dir.exists(): + backup_dir.mkdir(parents=True) + log_info(f"Created backup directory: {backup_dir}") + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_file = backup_dir / f".env.backup_{timestamp}" + shutil.copy2(env_file, backup_file) + log_success(f"Backed up existing .env to {backup_file}") + + +def analyze_value_change(current: str, recommended: str) -> str | None: + """Analyse what kind of change occurred between two env values. + + Args: + current: Value currently set in .env. + recommended: Value present in .env.example. + + Returns: + A human-readable description string, or None when no analysis applies. + """ + use_colors = supports_color() + + def colorize(color: str, text: str) -> str: + return f"{color}{text}{NC}" if use_colors else text + + if not current and recommended: + return colorize(RED, " -> Setting from empty to recommended value") + if current and not recommended: + return colorize(RED, " -> Recommended value changed to empty") + + # Numeric comparison + if re.fullmatch(r"\d+", current) and re.fullmatch(r"\d+", recommended): + cur_int, rec_int = int(current), int(recommended) + if cur_int < rec_int: + return colorize(BLUE, f" -> Numeric increase ({current} < {recommended})") + if cur_int > rec_int: + return colorize(YELLOW, f" -> Numeric decrease ({current} > {recommended})") + return None + + # Boolean comparison + if current.lower() in {"true", "false"} and recommended.lower() in { + "true", + "false", + }: + if current.lower() != recommended.lower(): + return colorize(BLUE, f" -> Boolean value change ({current} -> {recommended})") + return None + + # URL / endpoint + if current.startswith(("http://", "https://")) or recommended.startswith(("http://", "https://")): + return colorize(BLUE, " -> URL/endpoint change") + + # File path + if current.startswith("/") or recommended.startswith("/"): + return colorize(BLUE, " -> File path change") + + # String length + if len(current) != len(recommended): + return colorize( + YELLOW, + f" -> String length change ({len(current)} -> {len(recommended)} characters)", + ) + + return None + + +def detect_differences(env_vars: dict[str, str], example_vars: dict[str, str]) -> dict[str, tuple[str, str]]: + """Find variables whose values differ between .env and .env.example. + + Only variables present in *both* files are compared; new or removed + variables are handled by separate functions. + + Args: + env_vars: Parsed key/value pairs from .env. + example_vars: Parsed key/value pairs from .env.example. + + Returns: + Mapping of key -> (env_value, example_value) for every key whose + values differ. + """ + log_info("Detecting differences between .env and .env.example...") + + diffs: dict[str, tuple[str, str]] = {} + for key, example_value in example_vars.items(): + if key in env_vars and env_vars[key] != example_value: + diffs[key] = (env_vars[key], example_value) + + if diffs: + log_success(f"Detected differences in {len(diffs)} environment variables") + show_differences_detail(diffs) + else: + log_info("No differences detected") + + return diffs + + +def show_differences_detail(diffs: dict[str, tuple[str, str]]) -> None: + """Print a formatted table of differing environment variables. + + Args: + diffs: Mapping of key -> (current_value, recommended_value). + """ + use_colors = supports_color() + + log_info("") + log_info("=== Environment Variable Differences ===") + + if not diffs: + log_info("No differences to display") + return + + for count, (key, (env_value, example_value)) in enumerate(diffs.items(), start=1): + print() + if use_colors: + print(f"{YELLOW}[{count}] {key}{NC}") + print(f" {GREEN}.env (current){NC} : {env_value}") + print(f" {BLUE}.env.example (recommended){NC} : {example_value}") + else: + print(f"[{count}] {key}") + print(f" .env (current) : {env_value}") + print(f" .env.example (recommended) : {example_value}") + + analysis = analyze_value_change(env_value, example_value) + if analysis: + print(analysis) + + print() + log_info("=== Difference Analysis Complete ===") + log_info("Note: Consider changing to the recommended values above.") + log_info("Current implementation preserves .env values.") + print() + + +def detect_removed_variables(env_vars: dict[str, str], example_vars: dict[str, str]) -> list[str]: + """Identify variables present in .env but absent from .env.example. + + Args: + env_vars: Parsed key/value pairs from .env. + example_vars: Parsed key/value pairs from .env.example. + + Returns: + Sorted list of variable names that no longer appear in .env.example. + """ + log_info("Detecting removed environment variables...") + + removed = sorted(set(env_vars) - set(example_vars)) + + if removed: + log_warning("The following environment variables have been removed from .env.example:") + for var in removed: + log_warning(f" - {var}") + log_warning("Consider manually removing these variables from .env") + else: + log_success("No removed environment variables found") + + return removed + + +def sync_env_file(work_dir: Path, env_vars: dict[str, str], diffs: dict[str, tuple[str, str]]) -> None: + """Rewrite .env based on .env.example while preserving custom values. + + The output file follows the exact line structure of .env.example + (preserving comments, blank lines, and ordering). For every variable + that exists in .env with a different value from the example, the + current .env value is kept. Variables that are new in .env.example + (not present in .env at all) are added with the example's default. + + Args: + work_dir: Directory containing .env and .env.example. + env_vars: Parsed key/value pairs from the original .env. + diffs: Keys whose .env values differ from .env.example (to preserve). + """ + log_info("Starting partial synchronization of .env file...") + + example_file = work_dir / ".env.example" + new_env_file = work_dir / ".env.new" + + # Keys whose current .env value should override the example default + preserved_keys: set[str] = set(diffs.keys()) + + preserved_count = 0 + updated_count = 0 + + env_var_pattern = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)\s*=") + + with ( + example_file.open(encoding="utf-8") as src, + new_env_file.open("w", encoding="utf-8") as dst, + ): + for line in src: + raw_line = line.rstrip("\n") + match = env_var_pattern.match(raw_line) + if match: + key = match.group(1) + if key in preserved_keys: + # Write the preserved value from .env + dst.write(f"{key}={env_vars[key]}\n") + log_info(f" Preserved: {key} (.env value)") + preserved_count += 1 + else: + # Use the example value (covers new vars and unchanged ones) + dst.write(line if line.endswith("\n") else raw_line + "\n") + updated_count += 1 + else: + # Blank line, comment, or non-variable line — keep as-is + dst.write(line if line.endswith("\n") else raw_line + "\n") + + # Atomically replace the original .env + try: + new_env_file.replace(work_dir / ".env") + except OSError as exc: + log_error(f"Failed to replace .env file: {exc}") + new_env_file.unlink(missing_ok=True) + sys.exit(1) + + log_success("Successfully created new .env file") + log_success("Partial synchronization of .env file completed") + log_info(f" Preserved .env values: {preserved_count}") + log_info(f" Updated to .env.example values: {updated_count}") + + +def show_statistics(work_dir: Path) -> None: + """Print a summary of variable counts from both env files. + + Args: + work_dir: Directory containing .env and .env.example. + """ + log_info("Synchronization statistics:") + + example_file = work_dir / ".env.example" + env_file = work_dir / ".env" + + example_count = len(parse_env_file(example_file)) if example_file.exists() else 0 + env_count = len(parse_env_file(env_file)) if env_file.exists() else 0 + + log_info(f" .env.example environment variables: {example_count}") + log_info(f" .env environment variables: {env_count}") + + +def build_arg_parser() -> argparse.ArgumentParser: + """Build and return the CLI argument parser. + + Returns: + Configured ArgumentParser instance. + """ + parser = argparse.ArgumentParser( + prog="dify-env-sync", + description=( + "Synchronize .env with .env.example: add new variables, " + "preserve custom values, and report removed variables." + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=( + "Examples:\n" + " # Run from the docker/ directory (default)\n" + " python dify-env-sync.py\n\n" + " # Specify a custom working directory\n" + " python dify-env-sync.py --dir /path/to/docker\n" + ), + ) + parser.add_argument( + "--dir", + metavar="DIRECTORY", + default=".", + help="Working directory containing .env and .env.example (default: current directory)", + ) + parser.add_argument( + "--no-backup", + action="store_true", + default=False, + help="Skip creating a timestamped backup of the existing .env file", + ) + return parser + + +def main() -> None: + """Orchestrate the complete environment variable synchronization process.""" + parser = build_arg_parser() + args = parser.parse_args() + + work_dir = Path(args.dir).resolve() + + log_info("=== Dify Environment Variables Synchronization Script ===") + log_info(f"Execution started: {datetime.now()}") + log_info(f"Working directory: {work_dir}") + + # 1. Verify prerequisites + check_files(work_dir) + + # 2. Backup existing .env + if not args.no_backup: + create_backup(work_dir) + + # 3. Parse both files + env_vars = parse_env_file(work_dir / ".env") + example_vars = parse_env_file(work_dir / ".env.example") + + # 4. Report differences (values that changed in the example) + diffs = detect_differences(env_vars, example_vars) + + # 5. Report variables removed from the example + detect_removed_variables(env_vars, example_vars) + + # 6. Rewrite .env + sync_env_file(work_dir, env_vars, diffs) + + # 7. Print summary statistics + show_statistics(work_dir) + + log_success("=== Synchronization process completed successfully ===") + log_info(f"Execution finished: {datetime.now()}") + + +if __name__ == "__main__": + main() diff --git a/480T/dify/dify-env-sync.sh b/480T/dify/dify-env-sync.sh new file mode 100755 index 0000000..edeeae3 --- /dev/null +++ b/480T/dify/dify-env-sync.sh @@ -0,0 +1,465 @@ +#!/bin/bash + +# ================================================================ +# Dify Environment Variables Synchronization Script +# +# Features: +# - Synchronize latest settings from .env.example to .env +# - Preserve custom settings in existing .env +# - Add new environment variables +# - Detect removed environment variables +# - Create backup files +# ================================================================ + +set -eo pipefail # Exit on error and pipe failures (safer for complex variable handling) + +# Error handling function +# Arguments: +# $1 - Line number where error occurred +# $2 - Error code +handle_error() { + local line_no=$1 + local error_code=$2 + echo -e "\033[0;31m[ERROR]\033[0m Script error: line $line_no with error code $error_code" >&2 + echo -e "\033[0;31m[ERROR]\033[0m Debug info: current working directory $(pwd)" >&2 + exit $error_code +} + +# Set error trap +trap 'handle_error ${LINENO} $?' ERR + +# Color settings for output +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Logging functions +# Print informational message in blue +# Arguments: $1 - Message to print +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +# Print success message in green +# Arguments: $1 - Message to print +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +# Print warning message in yellow +# Arguments: $1 - Message to print +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" >&2 +} + +# Print error message in red to stderr +# Arguments: $1 - Message to print +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +# Check for required files and create .env if missing +# Verifies that .env.example exists and creates .env from template if needed +check_files() { + log_info "Checking required files..." + + if [[ ! -f ".env.example" ]]; then + log_error ".env.example file not found" + exit 1 + fi + + if [[ ! -f ".env" ]]; then + log_warning ".env file does not exist. Creating from .env.example." + cp ".env.example" ".env" + log_success ".env file created" + fi + + log_success "Required files verified" +} + +# Create timestamped backup of .env file +# Creates env-backup directory if needed and backs up current .env file +create_backup() { + local timestamp=$(date +"%Y%m%d_%H%M%S") + local backup_dir="env-backup" + + # Create backup directory if it doesn't exist + if [[ ! -d "$backup_dir" ]]; then + mkdir -p "$backup_dir" + log_info "Created backup directory: $backup_dir" + fi + + if [[ -f ".env" ]]; then + local backup_file="${backup_dir}/.env.backup_${timestamp}" + cp ".env" "$backup_file" + log_success "Backed up existing .env to $backup_file" + fi +} + +# Detect differences between .env and .env.example (optimized for large files) +detect_differences() { + log_info "Detecting differences between .env and .env.example..." + + # Create secure temporary directory + local temp_dir=$(mktemp -d) + local temp_diff="$temp_dir/env_diff" + + # Store diff file path as global variable + declare -g DIFF_FILE="$temp_diff" + declare -g TEMP_DIR="$temp_dir" + + # Initialize difference file + > "$temp_diff" + + # Use awk for efficient comparison (much faster for large files) + local diff_count=$(awk -F= ' + BEGIN { OFS="\x01" } + FNR==NR { + if (!/^[[:space:]]*#/ && !/^[[:space:]]*$/ && /=/) { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1) + key = $1 + value = substr($0, index($0,"=")+1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) + env_values[key] = value + } + next + } + { + if (!/^[[:space:]]*#/ && !/^[[:space:]]*$/ && /=/) { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1) + key = $1 + example_value = substr($0, index($0,"=")+1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", example_value) + + if (key in env_values && env_values[key] != example_value) { + print key, env_values[key], example_value > "'$temp_diff'" + diff_count++ + } + } + } + END { print diff_count } + ' .env .env.example) + + if [[ $diff_count -gt 0 ]]; then + log_success "Detected differences in $diff_count environment variables" + # Show detailed differences + show_differences_detail + else + log_info "No differences detected" + fi +} + +# Parse environment variable line +# Extracts key-value pairs from .env file format lines +# Arguments: +# $1 - Line to parse +# Returns: +# 0 - Success, outputs "key|value" format +# 1 - Skip (empty line, comment, or invalid format) +parse_env_line() { + local line="$1" + local key="" + local value="" + + # Skip empty lines or comment lines + [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && return 1 + + # Split by = + if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then + key="${BASH_REMATCH[1]}" + value="${BASH_REMATCH[2]}" + + # Remove leading and trailing whitespace + key=$(echo "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + value=$(echo "$value" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + + if [[ -n "$key" ]]; then + echo "$key|$value" + return 0 + fi + fi + + return 1 +} + +# Show detailed differences +show_differences_detail() { + log_info "" + log_info "=== Environment Variable Differences ===" + + # Read differences from the already created diff file + if [[ ! -s "$DIFF_FILE" ]]; then + log_info "No differences to display" + return + fi + + # Display differences + local count=1 + while IFS=$'\x01' read -r key env_value example_value; do + echo "" + echo -e "${YELLOW}[$count] $key${NC}" + echo -e " ${GREEN}.env (current)${NC} : ${env_value}" + echo -e " ${BLUE}.env.example (recommended)${NC}: ${example_value}" + + # Analyze value changes + analyze_value_change "$env_value" "$example_value" + ((count++)) + done < "$DIFF_FILE" + + echo "" + log_info "=== Difference Analysis Complete ===" + log_info "Note: Consider changing to the recommended values above." + log_info "Current implementation preserves .env values." + echo "" +} + +# Analyze value changes +analyze_value_change() { + local current_value="$1" + local recommended_value="$2" + + # Analyze value characteristics + local analysis="" + + # Empty value check + if [[ -z "$current_value" && -n "$recommended_value" ]]; then + analysis=" ${RED}→ Setting from empty to recommended value${NC}" + elif [[ -n "$current_value" && -z "$recommended_value" ]]; then + analysis=" ${RED}→ Recommended value changed to empty${NC}" + # Numeric check - using arithmetic evaluation for robust comparison + elif [[ "$current_value" =~ ^[0-9]+$ && "$recommended_value" =~ ^[0-9]+$ ]]; then + # Use arithmetic evaluation to handle leading zeros correctly + if (( 10#$current_value < 10#$recommended_value )); then + analysis=" ${BLUE}→ Numeric increase (${current_value} < ${recommended_value})${NC}" + elif (( 10#$current_value > 10#$recommended_value )); then + analysis=" ${YELLOW}→ Numeric decrease (${current_value} > ${recommended_value})${NC}" + fi + # Boolean check + elif [[ "$current_value" =~ ^(true|false)$ && "$recommended_value" =~ ^(true|false)$ ]]; then + if [[ "$current_value" != "$recommended_value" ]]; then + analysis=" ${BLUE}→ Boolean value change (${current_value} → ${recommended_value})${NC}" + fi + # URL/endpoint check + elif [[ "$current_value" =~ ^https?:// || "$recommended_value" =~ ^https?:// ]]; then + analysis=" ${BLUE}→ URL/endpoint change${NC}" + # File path check + elif [[ "$current_value" =~ ^/ || "$recommended_value" =~ ^/ ]]; then + analysis=" ${BLUE}→ File path change${NC}" + else + # Length comparison + local current_len=${#current_value} + local recommended_len=${#recommended_value} + if [[ $current_len -ne $recommended_len ]]; then + analysis=" ${YELLOW}→ String length change (${current_len} → ${recommended_len} characters)${NC}" + fi + fi + + if [[ -n "$analysis" ]]; then + echo -e "$analysis" + fi +} + +# Synchronize .env file with .env.example while preserving custom values +# Creates a new .env file based on .env.example structure, preserving existing custom values +# Global variables used: DIFF_FILE, TEMP_DIR +sync_env_file() { + log_info "Starting partial synchronization of .env file..." + + local new_env_file=".env.new" + local preserved_count=0 + local updated_count=0 + + # Pre-process diff file for efficient lookup + local lookup_file="" + if [[ -f "$DIFF_FILE" && -s "$DIFF_FILE" ]]; then + lookup_file="${DIFF_FILE}.lookup" + # Create sorted lookup file for fast search + sort "$DIFF_FILE" > "$lookup_file" + log_info "Created lookup file for $(wc -l < "$DIFF_FILE") preserved values" + fi + + # Use AWK for efficient processing (much faster than bash loop for large files) + log_info "Processing $(wc -l < .env.example) lines with AWK..." + + local preserved_keys_file="${TEMP_DIR}/preserved_keys" + local awk_preserved_count_file="${TEMP_DIR}/awk_preserved_count" + local awk_updated_count_file="${TEMP_DIR}/awk_updated_count" + + awk -F'=' -v lookup_file="$lookup_file" -v preserved_file="$preserved_keys_file" \ + -v preserved_count_file="$awk_preserved_count_file" -v updated_count_file="$awk_updated_count_file" ' + BEGIN { + preserved_count = 0 + updated_count = 0 + + # Load preserved values if lookup file exists + if (lookup_file != "") { + while ((getline line < lookup_file) > 0) { + split(line, parts, "\x01") + key = parts[1] + value = parts[2] + preserved_values[key] = value + } + close(lookup_file) + } + } + + # Process each line + { + # Check if this is an environment variable line + if (/^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/) { + # Extract key + key = $1 + gsub(/^[[:space:]]+|[[:space:]]+$/, "", key) + + # Check if key should be preserved + if (key in preserved_values) { + print key "=" preserved_values[key] + print key > preserved_file + preserved_count++ + } else { + print $0 + updated_count++ + } + } else { + # Not an env var line, preserve as-is + print $0 + } + } + + END { + print preserved_count > preserved_count_file + print updated_count > updated_count_file + } + ' .env.example > "$new_env_file" + + # Read counters and preserved keys + if [[ -f "$awk_preserved_count_file" ]]; then + preserved_count=$(cat "$awk_preserved_count_file") + fi + if [[ -f "$awk_updated_count_file" ]]; then + updated_count=$(cat "$awk_updated_count_file") + fi + + # Show what was preserved + if [[ -f "$preserved_keys_file" ]]; then + while read -r key; do + [[ -n "$key" ]] && log_info " Preserved: $key (.env value)" + done < "$preserved_keys_file" + fi + + # Clean up lookup file + [[ -n "$lookup_file" ]] && rm -f "$lookup_file" + + # Replace the original .env file + if mv "$new_env_file" ".env"; then + log_success "Successfully created new .env file" + else + log_error "Failed to replace .env file" + rm -f "$new_env_file" + return 1 + fi + + # Clean up difference file and temporary directory + if [[ -n "${TEMP_DIR:-}" ]]; then + rm -rf "${TEMP_DIR}" + unset TEMP_DIR + fi + if [[ -n "${DIFF_FILE:-}" ]]; then + unset DIFF_FILE + fi + + log_success "Partial synchronization of .env file completed" + log_info " Preserved .env values: $preserved_count" + log_info " Updated to .env.example values: $updated_count" +} + +# Detect removed environment variables +detect_removed_variables() { + log_info "Detecting removed environment variables..." + + if [[ ! -f ".env" ]]; then + return + fi + + # Use temporary files for efficient lookup + local temp_dir="${TEMP_DIR:-$(mktemp -d)}" + local temp_example_keys="$temp_dir/example_keys" + local temp_current_keys="$temp_dir/current_keys" + local cleanup_temp_dir="" + + # Set flag if we created a new temp directory + if [[ -z "${TEMP_DIR:-}" ]]; then + cleanup_temp_dir="$temp_dir" + fi + + # Get keys from .env.example and .env, sorted for comm + awk -F= '!/^[[:space:]]*#/ && /=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1); print $1}' .env.example | sort > "$temp_example_keys" + awk -F= '!/^[[:space:]]*#/ && /=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1); print $1}' .env | sort > "$temp_current_keys" + + # Get keys from existing .env and check for removals + local removed_vars=() + while IFS= read -r var; do + removed_vars+=("$var") + done < <(comm -13 "$temp_example_keys" "$temp_current_keys") + + # Clean up temporary files if we created a new temp directory + if [[ -n "$cleanup_temp_dir" ]]; then + rm -rf "$cleanup_temp_dir" + fi + + if [[ ${#removed_vars[@]} -gt 0 ]]; then + log_warning "The following environment variables have been removed from .env.example:" + for var in "${removed_vars[@]}"; do + log_warning " - $var" + done + log_warning "Consider manually removing these variables from .env" + else + log_success "No removed environment variables found" + fi +} + +# Show statistics +show_statistics() { + log_info "Synchronization statistics:" + + local total_example=$(grep -c "^[^#]*=" .env.example 2>/dev/null || echo "0") + local total_env=$(grep -c "^[^#]*=" .env 2>/dev/null || echo "0") + + log_info " .env.example environment variables: $total_example" + log_info " .env environment variables: $total_env" +} + +# Main execution function +# Orchestrates the complete synchronization process in the correct order +main() { + log_info "=== Dify Environment Variables Synchronization Script ===" + log_info "Execution started: $(date)" + + # Check prerequisites + check_files + + # Create backup + create_backup + + # Detect differences + detect_differences + + # Detect removed variables (before sync) + detect_removed_variables + + # Synchronize environment file + sync_env_file + + # Show statistics + show_statistics + + log_success "=== Synchronization process completed successfully ===" + log_info "Execution finished: $(date)" +} + +# Execute main function only when script is run directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/480T/dify/docker-compose-template.yaml b/480T/dify/docker-compose-template.yaml new file mode 100755 index 0000000..37d7185 --- /dev/null +++ b/480T/dify/docker-compose-template.yaml @@ -0,0 +1,1264 @@ +# Shared configuration using YAML anchors and env_file +x-shared-api-worker-config: &shared-api-worker-config + env_file: + - path: ./envs/core-services/shared.env + required: false + - path: ./envs/core-services/api.env + required: false + - path: ./envs/security.env + required: false + - path: ./envs/databases/db-postgres.env + required: false + - path: ./envs/databases/db-mysql.env + required: false + - path: ./envs/databases/redis.env + required: false + - path: ./envs/vectorstores/weaviate.env + required: false + - path: ./envs/vectorstores/qdrant.env + required: false + - path: ./envs/vectorstores/oceanbase.env + required: false + - path: ./envs/vectorstores/seekdb.env + required: false + - path: ./envs/vectorstores/couchbase.env + required: false + - path: ./envs/vectorstores/pgvector.env + required: false + - path: ./envs/vectorstores/vastbase.env + required: false + - path: ./envs/vectorstores/pgvecto-rs.env + required: false + - path: ./envs/vectorstores/chroma.env + required: false + - path: ./envs/vectorstores/iris.env + required: false + - path: ./envs/vectorstores/oracle.env + required: false + - path: ./envs/vectorstores/opengauss.env + required: false + - path: ./envs/vectorstores/myscale.env + required: false + - path: ./envs/vectorstores/matrixone.env + required: false + - path: ./envs/vectorstores/elasticsearch.env + required: false + - path: ./envs/vectorstores/opensearch.env + required: false + - path: ./envs/vectorstores/milvus.env + required: false + - path: ./envs/infrastructure/nginx.env + required: false + - path: ./envs/infrastructure/certbot.env + required: false + - path: ./envs/infrastructure/ssrf-proxy.env + required: false + - path: ./envs/infrastructure/etcd.env + required: false + - path: ./envs/infrastructure/minio.env + required: false + - path: ./envs/infrastructure/milvus-standalone.env + required: false + - ./.env + networks: + - ssrf_proxy_network + - default + restart: always + +x-shared-worker-config: &shared-worker-config + env_file: + - path: ./envs/core-services/shared.env + required: false + - path: ./envs/core-services/worker.env + required: false + - path: ./envs/security.env + required: false + - path: ./envs/databases/db-postgres.env + required: false + - path: ./envs/databases/db-mysql.env + required: false + - path: ./envs/databases/redis.env + required: false + - path: ./envs/vectorstores/weaviate.env + required: false + - path: ./envs/vectorstores/qdrant.env + required: false + - path: ./envs/vectorstores/oceanbase.env + required: false + - path: ./envs/vectorstores/seekdb.env + required: false + - path: ./envs/vectorstores/couchbase.env + required: false + - path: ./envs/vectorstores/pgvector.env + required: false + - path: ./envs/vectorstores/vastbase.env + required: false + - path: ./envs/vectorstores/pgvecto-rs.env + required: false + - path: ./envs/vectorstores/chroma.env + required: false + - path: ./envs/vectorstores/iris.env + required: false + - path: ./envs/vectorstores/oracle.env + required: false + - path: ./envs/vectorstores/opengauss.env + required: false + - path: ./envs/vectorstores/myscale.env + required: false + - path: ./envs/vectorstores/matrixone.env + required: false + - path: ./envs/vectorstores/elasticsearch.env + required: false + - path: ./envs/vectorstores/opensearch.env + required: false + - path: ./envs/vectorstores/milvus.env + required: false + - path: ./envs/infrastructure/nginx.env + required: false + - path: ./envs/infrastructure/certbot.env + required: false + - path: ./envs/infrastructure/ssrf-proxy.env + required: false + - path: ./envs/infrastructure/etcd.env + required: false + - path: ./envs/infrastructure/minio.env + required: false + - path: ./envs/infrastructure/milvus-standalone.env + required: false + - ./.env + networks: + - ssrf_proxy_network + - default + restart: always + +x-shared-worker-beat-config: &shared-worker-beat-config + env_file: + - path: ./envs/core-services/shared.env + required: false + - path: ./envs/core-services/worker-beat.env + required: false + - path: ./envs/security.env + required: false + - path: ./envs/databases/db-postgres.env + required: false + - path: ./envs/databases/db-mysql.env + required: false + - path: ./envs/databases/redis.env + required: false + - path: ./envs/vectorstores/weaviate.env + required: false + - path: ./envs/vectorstores/qdrant.env + required: false + - path: ./envs/vectorstores/oceanbase.env + required: false + - path: ./envs/vectorstores/seekdb.env + required: false + - path: ./envs/vectorstores/couchbase.env + required: false + - path: ./envs/vectorstores/pgvector.env + required: false + - path: ./envs/vectorstores/vastbase.env + required: false + - path: ./envs/vectorstores/pgvecto-rs.env + required: false + - path: ./envs/vectorstores/chroma.env + required: false + - path: ./envs/vectorstores/iris.env + required: false + - path: ./envs/vectorstores/oracle.env + required: false + - path: ./envs/vectorstores/opengauss.env + required: false + - path: ./envs/vectorstores/myscale.env + required: false + - path: ./envs/vectorstores/matrixone.env + required: false + - path: ./envs/vectorstores/elasticsearch.env + required: false + - path: ./envs/vectorstores/opensearch.env + required: false + - path: ./envs/vectorstores/milvus.env + required: false + - path: ./envs/infrastructure/nginx.env + required: false + - path: ./envs/infrastructure/certbot.env + required: false + - path: ./envs/infrastructure/ssrf-proxy.env + required: false + - path: ./envs/infrastructure/etcd.env + required: false + - path: ./envs/infrastructure/minio.env + required: false + - path: ./envs/infrastructure/milvus-standalone.env + required: false + - ./.env + networks: + - ssrf_proxy_network + - default + restart: always + +services: + # Init container to fix permissions + init_permissions: + image: busybox:latest + command: + - sh + - -c + - | + FLAG_FILE="/app/api/storage/.init_permissions" + if [ -f "$${FLAG_FILE}" ]; then + echo "Permissions already initialized. Exiting." + exit 0 + fi + echo "Initializing permissions for /app/api/storage" + chown -R 1001:1001 /app/api/storage && touch "$${FLAG_FILE}" + echo "Permissions initialized. Exiting." + volumes: + - ./volumes/app/storage:/app/api/storage + restart: "no" + + # API service + api: + <<: *shared-api-worker-config + image: langgenius/dify-api:1.16.0 + environment: + MODE: api + SENTRY_DSN: ${API_SENTRY_DSN:-} + SENTRY_TRACES_SAMPLE_RATE: ${API_SENTRY_TRACES_SAMPLE_RATE:-1.0} + SENTRY_PROFILES_SAMPLE_RATE: ${API_SENTRY_PROFILES_SAMPLE_RATE:-1.0} + PLUGIN_REMOTE_INSTALL_HOST: ${EXPOSE_PLUGIN_DEBUGGING_HOST:-localhost} + PLUGIN_REMOTE_INSTALL_PORT: ${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003} + PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} + PLUGIN_DAEMON_TIMEOUT: ${PLUGIN_DAEMON_TIMEOUT:-600.0} + INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} + AGENT_BACKEND_BASE_URL: ${AGENT_BACKEND_BASE_URL:-http://agent_backend:5050} + AGENT_BACKEND_STREAM_READ_TIMEOUT_SECONDS: ${AGENT_BACKEND_STREAM_READ_TIMEOUT_SECONDS:-30} + AGENT_BACKEND_STREAM_MAX_RECONNECTS: ${AGENT_BACKEND_STREAM_MAX_RECONNECTS:-3} + AGENT_BACKEND_RUN_TIMEOUT_SECONDS: ${AGENT_BACKEND_RUN_TIMEOUT_SECONDS:-1200} + depends_on: + init_permissions: + condition: service_completed_successfully + db_postgres: + condition: service_healthy + required: false + db_mysql: + condition: service_healthy + required: false + oceanbase: + condition: service_healthy + required: false + seekdb: + condition: service_healthy + required: false + redis: + condition: service_started + agent_backend: + condition: service_started + volumes: + # Mount the storage directory to the container, for storing user files. + - ./volumes/app/storage:/app/api/storage + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5001/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + networks: + - ssrf_proxy_network + - default + + # WebSocket service for workflow collaboration. + api_websocket: + <<: *shared-api-worker-config + image: langgenius/dify-api:1.16.0 + profiles: + - collaboration + environment: + MODE: api + SERVER_WORKER_AMOUNT: ${API_WEBSOCKET_WORKER_AMOUNT:-1} + SERVER_WORKER_CLASS: ${API_WEBSOCKET_WORKER_CLASS:-geventwebsocket.gunicorn.workers.GeventWebSocketWorker} + SERVER_WORKER_CONNECTIONS: ${API_WEBSOCKET_WORKER_CONNECTIONS:-1000} + GUNICORN_TIMEOUT: ${API_WEBSOCKET_GUNICORN_TIMEOUT:-360} + depends_on: + db_postgres: + condition: service_healthy + required: false + db_mysql: + condition: service_healthy + required: false + redis: + condition: service_started + networks: + - ssrf_proxy_network + - default + + # worker service + # The Celery worker for processing all queues (dataset, workflow, mail, etc.) + worker: + <<: *shared-worker-config + image: langgenius/dify-api:1.16.0 + environment: + MODE: worker + SENTRY_DSN: ${API_SENTRY_DSN:-} + SENTRY_TRACES_SAMPLE_RATE: ${API_SENTRY_TRACES_SAMPLE_RATE:-1.0} + SENTRY_PROFILES_SAMPLE_RATE: ${API_SENTRY_PROFILES_SAMPLE_RATE:-1.0} + PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} + INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} + AGENT_BACKEND_BASE_URL: ${AGENT_BACKEND_BASE_URL:-http://agent_backend:5050} + AGENT_BACKEND_STREAM_READ_TIMEOUT_SECONDS: ${AGENT_BACKEND_STREAM_READ_TIMEOUT_SECONDS:-30} + AGENT_BACKEND_STREAM_MAX_RECONNECTS: ${AGENT_BACKEND_STREAM_MAX_RECONNECTS:-3} + AGENT_BACKEND_RUN_TIMEOUT_SECONDS: ${AGENT_BACKEND_RUN_TIMEOUT_SECONDS:-1200} + depends_on: + init_permissions: + condition: service_completed_successfully + db_postgres: + condition: service_healthy + required: false + db_mysql: + condition: service_healthy + required: false + oceanbase: + condition: service_healthy + required: false + seekdb: + condition: service_healthy + required: false + redis: + condition: service_started + agent_backend: + condition: service_started + volumes: + # Mount the storage directory to the container, for storing user files. + - ./volumes/app/storage:/app/api/storage + healthcheck: + test: ["CMD-SHELL", "celery -A celery_healthcheck.celery inspect ping"] + interval: ${COMPOSE_WORKER_HEALTHCHECK_INTERVAL:-30s} + timeout: ${COMPOSE_WORKER_HEALTHCHECK_TIMEOUT:-30s} + retries: 3 + start_period: 60s + disable: ${COMPOSE_WORKER_HEALTHCHECK_DISABLED:-true} + networks: + - ssrf_proxy_network + - default + + # worker_beat service + # Celery beat for scheduling periodic tasks. + worker_beat: + <<: *shared-worker-beat-config + image: langgenius/dify-api:1.16.0 + environment: + MODE: beat + depends_on: + init_permissions: + condition: service_completed_successfully + db_postgres: + condition: service_healthy + required: false + db_mysql: + condition: service_healthy + required: false + oceanbase: + condition: service_healthy + required: false + seekdb: + condition: service_healthy + required: false + redis: + condition: service_started + healthcheck: + test: ["CMD-SHELL", "celery -A celery_healthcheck.celery inspect ping"] + interval: ${COMPOSE_WORKER_HEALTHCHECK_INTERVAL:-30s} + timeout: ${COMPOSE_WORKER_HEALTHCHECK_TIMEOUT:-30s} + retries: 3 + start_period: 60s + disable: ${COMPOSE_WORKER_HEALTHCHECK_DISABLED:-true} + networks: + - ssrf_proxy_network + - default + + # Frontend web application. + web: + image: langgenius/dify-web:1.16.0 + restart: always + env_file: + - path: ./envs/core-services/web.env + required: false + - path: ./envs/security.env + required: false + - ./.env + environment: + CONSOLE_API_URL: ${CONSOLE_API_URL:-} + SERVER_CONSOLE_API_URL: ${SERVER_CONSOLE_API_URL:-http://api:5001} + APP_API_URL: ${APP_API_URL:-} + AMPLITUDE_API_KEY: ${AMPLITUDE_API_KEY:-} + COOKIEYES_SITE_KEY: ${COOKIEYES_SITE_KEY:-} + NEXT_PUBLIC_COOKIE_DOMAIN: ${NEXT_PUBLIC_COOKIE_DOMAIN:-} + NEXT_PUBLIC_SOCKET_URL: ${NEXT_PUBLIC_SOCKET_URL:-ws://localhost} + SENTRY_DSN: ${WEB_SENTRY_DSN:-} + NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0} + EXPERIMENTAL_ENABLE_VINEXT: ${EXPERIMENTAL_ENABLE_VINEXT:-false} + TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000} + WORKFLOW_GENERATION_TIMEOUT_MS: ${WORKFLOW_GENERATION_TIMEOUT_MS:-180000} + CSP_WHITELIST: ${CSP_WHITELIST:-} + ALLOW_EMBED: ${ALLOW_EMBED:-false} + ALLOW_UNSAFE_DATA_SCHEME: ${ALLOW_UNSAFE_DATA_SCHEME:-false} + MARKETPLACE_API_URL: ${MARKETPLACE_API_URL:-https://marketplace.dify.ai} + MARKETPLACE_URL: ${MARKETPLACE_URL:-https://marketplace.dify.ai} + TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-10} + INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-4000} + LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} + MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} + MAX_PARALLEL_LIMIT: ${MAX_PARALLEL_LIMIT:-10} + MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-99} + MAX_TREE_DEPTH: ${MAX_TREE_DEPTH:-50} + ENABLE_WEBSITE_JINAREADER: ${ENABLE_WEBSITE_JINAREADER:-true} + ENABLE_WEBSITE_FIRECRAWL: ${ENABLE_WEBSITE_FIRECRAWL:-true} + ENABLE_WEBSITE_WATERCRAWL: ${ENABLE_WEBSITE_WATERCRAWL:-true} + + # The PostgreSQL database. + db_postgres: + image: postgres:15-alpine + profiles: + - postgresql + restart: always + environment: + POSTGRES_USER: ${DB_USERNAME:-postgres} + POSTGRES_PASSWORD: ${DB_PASSWORD:-difyai123456} + POSTGRES_DB: ${DB_DATABASE:-dify} + PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata} + command: > + postgres -c 'max_connections=${POSTGRES_MAX_CONNECTIONS:-100}' + -c 'shared_buffers=${POSTGRES_SHARED_BUFFERS:-128MB}' + -c 'work_mem=${POSTGRES_WORK_MEM:-4MB}' + -c 'maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-64MB}' + -c 'effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-4096MB}' + -c 'statement_timeout=${POSTGRES_STATEMENT_TIMEOUT:-0}' + -c 'idle_in_transaction_session_timeout=${POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT:-0}' + volumes: + - ./volumes/db/data:/var/lib/postgresql/data + healthcheck: + test: + [ + "CMD", + "pg_isready", + "-h", + "db_postgres", + "-U", + "${DB_USERNAME:-postgres}", + "-d", + "${DB_DATABASE:-dify}", + ] + interval: 1s + timeout: 3s + retries: 60 + + # The mysql database. + db_mysql: + image: mysql:8.0 + profiles: + - mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-difyai123456} + MYSQL_DATABASE: ${DB_DATABASE:-dify} + command: > + --max_connections=${MYSQL_MAX_CONNECTIONS:-1000} + --innodb_buffer_pool_size=${MYSQL_INNODB_BUFFER_POOL_SIZE:-512M} + --innodb_log_file_size=${MYSQL_INNODB_LOG_FILE_SIZE:-128M} + --innodb_flush_log_at_trx_commit=${MYSQL_INNODB_FLUSH_LOG_AT_TRX_COMMIT:-2} + volumes: + - ${MYSQL_HOST_VOLUME:-./volumes/mysql/data}:/var/lib/mysql + healthcheck: + test: + [ + "CMD", + "mysqladmin", + "ping", + "-u", + "root", + "-p${DB_PASSWORD:-difyai123456}", + ] + interval: 1s + timeout: 3s + retries: 30 + + # The redis cache. + redis: + image: redis:6-alpine + restart: always + environment: + REDISCLI_AUTH: ${REDIS_PASSWORD:-difyai123456} + volumes: + # Mount the redis data directory to the container. + - ./volumes/redis/data:/data + # Set the redis password when startup redis server. + command: redis-server --requirepass ${REDIS_PASSWORD:-difyai123456} + healthcheck: + test: + [ + "CMD-SHELL", + "redis-cli -a ${REDIS_PASSWORD:-difyai123456} ping | grep -q PONG", + ] + + # The DifySandbox + sandbox: + image: langgenius/dify-sandbox:0.2.15 + restart: always + env_file: + - path: ./envs/core-services/sandbox.env + required: false + - path: ./envs/security.env + required: false + - ./.env + environment: + # The DifySandbox configurations + # Make sure you are changing this key for your deployment with a strong key. + # You can generate a strong key using `openssl rand -base64 42`. + API_KEY: ${SANDBOX_API_KEY:-dify-sandbox} + GIN_MODE: ${SANDBOX_GIN_MODE:-release} + WORKER_TIMEOUT: ${SANDBOX_WORKER_TIMEOUT:-15} + ENABLE_NETWORK: ${SANDBOX_ENABLE_NETWORK:-true} + HTTP_PROXY: ${SANDBOX_HTTP_PROXY:-http://ssrf_proxy:3128} + HTTPS_PROXY: ${SANDBOX_HTTPS_PROXY:-http://ssrf_proxy:3128} + SANDBOX_PORT: ${SANDBOX_PORT:-8194} + PIP_MIRROR_URL: ${PIP_MIRROR_URL:-} + volumes: + - ./volumes/sandbox/dependencies:/dependencies + - ./volumes/sandbox/conf:/conf + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8194/health"] + networks: + - ssrf_proxy_network + + # Local sandbox for Dify Agent shell workspaces. + local_sandbox: + image: langgenius/dify-agent-local-sandbox:1.16.0 + restart: always + env_file: + - path: ./envs/core-services/local-sandbox.env + required: false + environment: + - SHELLCTL_AUTH_TOKEN=${DIFY_AGENT_SHELLCTL_AUTH_TOKEN:-} + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5004/healthz"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + networks: + - default + + # plugin daemon + plugin_daemon: + image: langgenius/dify-plugin-daemon:0.6.3-local + restart: always + env_file: + - path: ./envs/core-services/shared.env + required: false + - path: ./envs/core-services/plugin-daemon.env + required: false + - path: ./envs/security.env + required: false + - path: ./envs/databases/db-postgres.env + required: false + - path: ./envs/databases/db-mysql.env + required: false + - path: ./envs/databases/redis.env + required: false + - ./.env + networks: + - ssrf_proxy_network + - default + environment: + DB_DATABASE: ${DB_PLUGIN_DATABASE:-dify_plugin} + DB_SSL_MODE: ${DB_SSL_MODE:-disable} + SERVER_PORT: ${PLUGIN_DAEMON_PORT:-5002} + SERVER_KEY: ${PLUGIN_DAEMON_KEY:-lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi} + MAX_PLUGIN_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} + PPROF_ENABLED: ${PLUGIN_PPROF_ENABLED:-false} + DIFY_INNER_API_URL: ${PLUGIN_DIFY_INNER_API_URL:-http://api:5001} + DIFY_INNER_API_KEY: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} + PLUGIN_REMOTE_INSTALLING_HOST: ${PLUGIN_DEBUGGING_HOST:-0.0.0.0} + PLUGIN_REMOTE_INSTALLING_PORT: ${PLUGIN_DEBUGGING_PORT:-5003} + PLUGIN_WORKING_PATH: ${PLUGIN_WORKING_PATH:-/app/storage/cwd} + FORCE_VERIFYING_SIGNATURE: ${FORCE_VERIFYING_SIGNATURE:-true} + PYTHON_ENV_INIT_TIMEOUT: ${PLUGIN_PYTHON_ENV_INIT_TIMEOUT:-120} + PLUGIN_MAX_EXECUTION_TIMEOUT: ${PLUGIN_MAX_EXECUTION_TIMEOUT:-600} + PLUGIN_STDIO_BUFFER_SIZE: ${PLUGIN_STDIO_BUFFER_SIZE:-1024} + PLUGIN_STDIO_MAX_BUFFER_SIZE: ${PLUGIN_STDIO_MAX_BUFFER_SIZE:-5242880} + PIP_MIRROR_URL: ${PIP_MIRROR_URL:-} + PLUGIN_STORAGE_TYPE: ${PLUGIN_STORAGE_TYPE:-local} + PLUGIN_STORAGE_LOCAL_ROOT: ${PLUGIN_STORAGE_LOCAL_ROOT:-/app/storage} + PLUGIN_INSTALLED_PATH: ${PLUGIN_INSTALLED_PATH:-plugin} + PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} + PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} + PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} + S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} + S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false} + S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} + S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} + AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} + AWS_SECRET_KEY: ${PLUGIN_AWS_SECRET_KEY:-} + AWS_REGION: ${PLUGIN_AWS_REGION:-} + AZURE_BLOB_STORAGE_CONNECTION_STRING: ${PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING:-} + AZURE_BLOB_STORAGE_CONTAINER_NAME: ${PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME:-} + TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} + TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} + TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} + ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} + ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} + ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} + ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} + ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} + ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} + VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} + VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} + VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} + VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} + SENTRY_ENABLED: ${PLUGIN_SENTRY_ENABLED:-false} + SENTRY_DSN: ${PLUGIN_SENTRY_DSN:-} + ports: + - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}" + volumes: + - ./volumes/plugin_daemon:/app/storage + depends_on: + db_postgres: + condition: service_healthy + required: false + db_mysql: + condition: service_healthy + required: false + oceanbase: + condition: service_healthy + required: false + seekdb: + condition: service_healthy + required: false + + # Dify Agent backend service. + agent_backend: + image: langgenius/dify-agent-backend:1.16.0 + restart: always + env_file: + - path: ./envs/core-services/dify-agent.env + required: false + - path: ./envs/security.env + required: false + - path: ./envs/databases/redis.env + required: false + - ./.env + environment: + DIFY_AGENT_REDIS_URL: ${DIFY_AGENT_REDIS_URL:-redis://:${REDIS_PASSWORD:-difyai123456}@redis:6379/2} + DIFY_AGENT_REDIS_PREFIX: ${DIFY_AGENT_REDIS_PREFIX:-dify-agent} + DIFY_AGENT_PLUGIN_DAEMON_URL: ${DIFY_AGENT_PLUGIN_DAEMON_URL:-${PLUGIN_DAEMON_URL:-http://plugin_daemon:5002}} + DIFY_AGENT_PLUGIN_DAEMON_API_KEY: ${DIFY_AGENT_PLUGIN_DAEMON_API_KEY:-${PLUGIN_DAEMON_KEY:-lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi}} + DIFY_AGENT_INNER_API_URL: ${DIFY_AGENT_INNER_API_URL:-${PLUGIN_DIFY_INNER_API_URL:-http://api:5001}} + DIFY_AGENT_INNER_API_KEY: ${DIFY_AGENT_INNER_API_KEY:-${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1}} + DIFY_AGENT_SHELLCTL_ENTRYPOINT: ${DIFY_AGENT_SHELLCTL_ENTRYPOINT:-http://local_sandbox:5004} + DIFY_AGENT_SHELLCTL_AUTH_TOKEN: ${DIFY_AGENT_SHELLCTL_AUTH_TOKEN:-} + DIFY_AGENT_STUB_API_BASE_URL: ${DIFY_AGENT_STUB_API_BASE_URL:-http://agent_backend:5050/agent-stub} + # This is security-sensitive: it derives the JWE encryption key for Agent Stub bearer tokens. + # Replace this development default in production. + # Generate one with: python -c 'import secrets; print(secrets.token_urlsafe(32))' + DIFY_AGENT_SERVER_SECRET_KEY: ${DIFY_AGENT_SERVER_SECRET_KEY:-MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY} + DIFY_AGENT_SHUTDOWN_GRACE_SECONDS: ${DIFY_AGENT_SHUTDOWN_GRACE_SECONDS:-30} + DIFY_AGENT_RUN_RETENTION_SECONDS: ${DIFY_AGENT_RUN_RETENTION_SECONDS:-259200} + depends_on: + redis: + condition: service_started + plugin_daemon: + condition: service_started + local_sandbox: + condition: service_started + networks: + - default + + # ssrf_proxy server + # for more information, please refer to + # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F + ssrf_proxy: + image: ubuntu/squid:latest + restart: always + volumes: + - ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template + - ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh + entrypoint: + [ + "sh", + "-c", + "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh", + ] + environment: + # pls clearly modify the squid env vars to fit your network environment. + HTTP_PORT: ${SSRF_HTTP_PORT:-3128} + COREDUMP_DIR: ${SSRF_COREDUMP_DIR:-/var/spool/squid} + SSRF_PROXY_ALLOW_PRIVATE_IPS: ${SSRF_PROXY_ALLOW_PRIVATE_IPS:-} + SSRF_PROXY_ALLOW_PRIVATE_DOMAINS: ${SSRF_PROXY_ALLOW_PRIVATE_DOMAINS:-} + networks: + - ssrf_proxy_network + - default + + # Certbot service + # use `docker-compose --profile certbot up` to start the certbot service. + certbot: + image: certbot/certbot + profiles: + - certbot + volumes: + - ./volumes/certbot/conf:/etc/letsencrypt + - ./volumes/certbot/www:/var/www/html + - ./volumes/certbot/logs:/var/log/letsencrypt + - ./volumes/certbot/conf/live:/etc/letsencrypt/live + - ./certbot/update-cert.template.txt:/update-cert.template.txt + - ./certbot/docker-entrypoint.sh:/docker-entrypoint.sh + environment: + - CERTBOT_EMAIL=${CERTBOT_EMAIL:-} + - CERTBOT_DOMAIN=${CERTBOT_DOMAIN:-} + - CERTBOT_OPTIONS=${CERTBOT_OPTIONS:-} + entrypoint: ["/docker-entrypoint.sh"] + command: ["tail", "-f", "/dev/null"] + + # The nginx reverse proxy. + # used for reverse proxying the API service and Web service. + nginx: + image: nginx:latest + restart: always + volumes: + - ./nginx/nginx.conf.template:/etc/nginx/nginx.conf.template + - ./nginx/proxy.conf.template:/etc/nginx/proxy.conf.template + - ./nginx/https.conf.template:/etc/nginx/https.conf.template + - ./nginx/conf.d:/etc/nginx/conf.d + - ./nginx/docker-entrypoint.sh:/docker-entrypoint-mount.sh + - ./nginx/ssl:/etc/ssl # cert dir (legacy) + - ./volumes/certbot/conf/live:/etc/letsencrypt/live # cert dir (with certbot container) + - ./volumes/certbot/conf:/etc/letsencrypt + - ./volumes/certbot/www:/var/www/html + entrypoint: + [ + "sh", + "-c", + "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh", + ] + environment: + NGINX_SERVER_NAME: ${NGINX_SERVER_NAME:-_} + NGINX_HTTPS_ENABLED: ${NGINX_HTTPS_ENABLED:-false} + NGINX_SSL_PORT: ${NGINX_SSL_PORT:-443} + NGINX_PORT: ${NGINX_PORT:-80} + # You're required to add your own SSL certificates/keys to the `./nginx/ssl` directory + # and modify the env vars below in .env if HTTPS_ENABLED is true. + NGINX_SSL_CERT_FILENAME: ${NGINX_SSL_CERT_FILENAME:-dify.crt} + NGINX_SSL_CERT_KEY_FILENAME: ${NGINX_SSL_CERT_KEY_FILENAME:-dify.key} + NGINX_SSL_PROTOCOLS: ${NGINX_SSL_PROTOCOLS:-TLSv1.2 TLSv1.3} + NGINX_WORKER_PROCESSES: ${NGINX_WORKER_PROCESSES:-auto} + NGINX_CLIENT_MAX_BODY_SIZE: ${NGINX_CLIENT_MAX_BODY_SIZE:-100M} + NGINX_KEEPALIVE_TIMEOUT: ${NGINX_KEEPALIVE_TIMEOUT:-65} + NGINX_PROXY_READ_TIMEOUT: ${NGINX_PROXY_READ_TIMEOUT:-3600s} + NGINX_PROXY_SEND_TIMEOUT: ${NGINX_PROXY_SEND_TIMEOUT:-3600s} + NGINX_ENABLE_CERTBOT_CHALLENGE: ${NGINX_ENABLE_CERTBOT_CHALLENGE:-false} + NGINX_SOCKET_IO_UPSTREAM: ${NGINX_SOCKET_IO_UPSTREAM:-api_websocket:5001} + CERTBOT_DOMAIN: ${CERTBOT_DOMAIN:-} + depends_on: + - api + - web + ports: + - "${EXPOSE_NGINX_PORT:-80}:${NGINX_PORT:-80}" + - "${EXPOSE_NGINX_SSL_PORT:-443}:${NGINX_SSL_PORT:-443}" + + # The Weaviate vector store. + weaviate: + image: semitechnologies/weaviate:1.27.0 + profiles: + - weaviate + restart: always + volumes: + # Mount the Weaviate data directory to the con tainer. + - ./volumes/weaviate:/var/lib/weaviate + environment: + # The Weaviate configurations + # You can refer to the [Weaviate](https://weaviate.io/developers/weaviate/config-refs/env-vars) documentation for more information. + PERSISTENCE_DATA_PATH: ${WEAVIATE_PERSISTENCE_DATA_PATH:-/var/lib/weaviate} + QUERY_DEFAULTS_LIMIT: ${WEAVIATE_QUERY_DEFAULTS_LIMIT:-25} + AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: ${WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED:-false} + DEFAULT_VECTORIZER_MODULE: ${WEAVIATE_DEFAULT_VECTORIZER_MODULE:-none} + CLUSTER_HOSTNAME: ${WEAVIATE_CLUSTER_HOSTNAME:-node1} + AUTHENTICATION_APIKEY_ENABLED: ${WEAVIATE_AUTHENTICATION_APIKEY_ENABLED:-true} + AUTHENTICATION_APIKEY_ALLOWED_KEYS: ${WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS:-WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih} + AUTHENTICATION_APIKEY_USERS: ${WEAVIATE_AUTHENTICATION_APIKEY_USERS:-hello@dify.ai} + AUTHORIZATION_ADMINLIST_ENABLED: ${WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED:-true} + AUTHORIZATION_ADMINLIST_USERS: ${WEAVIATE_AUTHORIZATION_ADMINLIST_USERS:-hello@dify.ai} + DISABLE_TELEMETRY: ${WEAVIATE_DISABLE_TELEMETRY:-false} + ENABLE_TOKENIZER_GSE: ${WEAVIATE_ENABLE_TOKENIZER_GSE:-false} + ENABLE_TOKENIZER_KAGOME_JA: ${WEAVIATE_ENABLE_TOKENIZER_KAGOME_JA:-false} + ENABLE_TOKENIZER_KAGOME_KR: ${WEAVIATE_ENABLE_TOKENIZER_KAGOME_KR:-false} + + # OceanBase vector database + oceanbase: + image: oceanbase/oceanbase-ce:4.3.5-lts + container_name: oceanbase + profiles: + - oceanbase + restart: always + volumes: + - ./volumes/oceanbase/data:/root/ob + - ./volumes/oceanbase/conf:/root/.obd/cluster + - ./volumes/oceanbase/init.d:/root/boot/init.d + environment: + OB_MEMORY_LIMIT: ${OCEANBASE_MEMORY_LIMIT:-6G} + OB_SYS_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} + OB_TENANT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} + OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-difyai} + OB_SERVER_IP: 127.0.0.1 + MODE: mini + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + ports: + - "${OCEANBASE_VECTOR_PORT:-2881}:2881" + healthcheck: + test: + [ + "CMD-SHELL", + 'obclient -h127.0.0.1 -P2881 -uroot@test -p${OCEANBASE_VECTOR_PASSWORD:-difyai123456} -e "SELECT 1;"', + ] + interval: 10s + retries: 30 + start_period: 30s + timeout: 10s + + # seekdb vector database + seekdb: + image: oceanbase/seekdb:latest + container_name: seekdb + profiles: + - seekdb + restart: always + volumes: + - ./volumes/seekdb:/var/lib/oceanbase + environment: + ROOT_PASSWORD: ${OCEANBASE_VECTOR_PASSWORD:-difyai123456} + MEMORY_LIMIT: ${SEEKDB_MEMORY_LIMIT:-2G} + REPORTER: dify-ai-seekdb + ports: + - "${OCEANBASE_VECTOR_PORT:-2881}:2881" + healthcheck: + test: + [ + "CMD-SHELL", + 'mysql -h127.0.0.1 -P2881 -uroot -p${OCEANBASE_VECTOR_PASSWORD:-difyai123456} -e "SELECT 1;"', + ] + interval: 5s + retries: 60 + timeout: 5s + + # Qdrant vector store. + # (if used, you need to set VECTOR_STORE to qdrant in the api & worker service.) + qdrant: + image: langgenius/qdrant:v1.8.3 + profiles: + - qdrant + restart: always + volumes: + - ./volumes/qdrant:/qdrant/storage + environment: + QDRANT_API_KEY: ${QDRANT_API_KEY:-difyai123456} + + # The Couchbase vector store. + couchbase-server: + build: ./couchbase-server + profiles: + - couchbase + restart: always + environment: + - CLUSTER_NAME=dify_search + - COUCHBASE_ADMINISTRATOR_USERNAME=${COUCHBASE_USER:-Administrator} + - COUCHBASE_ADMINISTRATOR_PASSWORD=${COUCHBASE_PASSWORD:-password} + - COUCHBASE_BUCKET=${COUCHBASE_BUCKET_NAME:-Embeddings} + - COUCHBASE_BUCKET_RAMSIZE=512 + - COUCHBASE_RAM_SIZE=2048 + - COUCHBASE_EVENTING_RAM_SIZE=512 + - COUCHBASE_INDEX_RAM_SIZE=512 + - COUCHBASE_FTS_RAM_SIZE=1024 + hostname: couchbase-server + container_name: couchbase-server + working_dir: /opt/couchbase + stdin_open: true + tty: true + entrypoint: [""] + command: sh -c "/opt/couchbase/init/init-cbserver.sh" + volumes: + - ./volumes/couchbase/data:/opt/couchbase/var/lib/couchbase/data + healthcheck: + # ensure bucket was created before proceeding + test: + [ + "CMD-SHELL", + "curl -s -f -u Administrator:password http://localhost:8091/pools/default/buckets | grep -q '\\[{' || exit 1", + ] + interval: 10s + retries: 10 + start_period: 30s + timeout: 10s + + # The pgvector vector database. + pgvector: + image: pgvector/pgvector:pg16 + profiles: + - pgvector + restart: always + environment: + PGUSER: ${PGVECTOR_PGUSER:-postgres} + # The password for the default postgres user. + POSTGRES_PASSWORD: ${PGVECTOR_POSTGRES_PASSWORD:-difyai123456} + # The name of the default postgres database. + POSTGRES_DB: ${PGVECTOR_POSTGRES_DB:-dify} + # postgres data directory + PGDATA: ${PGVECTOR_PGDATA:-/var/lib/postgresql/data/pgdata} + # pg_bigm module for full text search + PG_BIGM: ${PGVECTOR_PG_BIGM:-false} + PG_BIGM_VERSION: ${PGVECTOR_PG_BIGM_VERSION:-1.2-20240606} + volumes: + - ./volumes/pgvector/data:/var/lib/postgresql/data + - ./pgvector/docker-entrypoint.sh:/docker-entrypoint.sh + entrypoint: ["/docker-entrypoint.sh"] + healthcheck: + test: ["CMD", "pg_isready"] + interval: 1s + timeout: 3s + retries: 30 + + # get image from https://www.vastdata.com.cn/ + vastbase: + image: vastdata/vastbase-vector + profiles: + - vastbase + restart: always + environment: + - VB_DBCOMPATIBILITY=PG + - VB_DB=dify + - VB_USERNAME=dify + - VB_PASSWORD=Difyai123456 + ports: + - "5434:5432" + volumes: + - ./vastbase/lic:/home/vastbase/vastbase/lic + - ./vastbase/data:/home/vastbase/data + - ./vastbase/backup:/home/vastbase/backup + - ./vastbase/backup_log:/home/vastbase/backup_log + healthcheck: + test: ["CMD", "pg_isready"] + interval: 1s + timeout: 3s + retries: 30 + + # pgvecto-rs vector store + pgvecto-rs: + image: tensorchord/pgvecto-rs:pg16-v0.3.0 + profiles: + - pgvecto-rs + restart: always + environment: + PGUSER: ${PGVECTOR_PGUSER:-postgres} + # The password for the default postgres user. + POSTGRES_PASSWORD: ${PGVECTOR_POSTGRES_PASSWORD:-difyai123456} + # The name of the default postgres database. + POSTGRES_DB: ${PGVECTOR_POSTGRES_DB:-dify} + # postgres data directory + PGDATA: ${PGVECTOR_PGDATA:-/var/lib/postgresql/data/pgdata} + volumes: + - ./volumes/pgvecto_rs/data:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready"] + interval: 1s + timeout: 3s + retries: 30 + + # Chroma vector database + chroma: + image: ghcr.io/chroma-core/chroma:0.5.20 + profiles: + - chroma + restart: always + volumes: + - ./volumes/chroma:/chroma/chroma + environment: + CHROMA_SERVER_AUTHN_CREDENTIALS: ${CHROMA_SERVER_AUTHN_CREDENTIALS:-difyai123456} + CHROMA_SERVER_AUTHN_PROVIDER: ${CHROMA_SERVER_AUTHN_PROVIDER:-chromadb.auth.token_authn.TokenAuthenticationServerProvider} + IS_PERSISTENT: ${CHROMA_IS_PERSISTENT:-TRUE} + + # InterSystems IRIS vector database + iris: + image: containers.intersystems.com/intersystems/iris-community:2025.3 + profiles: + - iris + container_name: iris + restart: always + init: true + ports: + - "${IRIS_SUPER_SERVER_PORT:-1972}:1972" + - "${IRIS_WEB_SERVER_PORT:-52773}:52773" + volumes: + - ./volumes/iris:/durable + - ./iris/iris-init.script:/iris-init.script + - ./iris/docker-entrypoint.sh:/custom-entrypoint.sh + entrypoint: ["/custom-entrypoint.sh"] + tty: true + environment: + TZ: ${IRIS_TIMEZONE:-UTC} + ISC_DATA_DIRECTORY: /durable/iris + + # Oracle vector database + oracle: + image: container-registry.oracle.com/database/free:latest + profiles: + - oracle + restart: always + volumes: + - source: oradata + type: volume + target: /opt/oracle/oradata + - ./startupscripts:/opt/oracle/scripts/startup + environment: + ORACLE_PWD: ${ORACLE_PWD:-Dify123456} + ORACLE_CHARACTERSET: ${ORACLE_CHARACTERSET:-AL32UTF8} + + # Milvus vector database services + etcd: + container_name: milvus-etcd + image: quay.io/coreos/etcd:v3.5.5 + profiles: + - milvus + environment: + ETCD_AUTO_COMPACTION_MODE: ${ETCD_AUTO_COMPACTION_MODE:-revision} + ETCD_AUTO_COMPACTION_RETENTION: ${ETCD_AUTO_COMPACTION_RETENTION:-1000} + ETCD_QUOTA_BACKEND_BYTES: ${ETCD_QUOTA_BACKEND_BYTES:-4294967296} + ETCD_SNAPSHOT_COUNT: ${ETCD_SNAPSHOT_COUNT:-50000} + volumes: + - ./volumes/milvus/etcd:/etcd + command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd + healthcheck: + test: ["CMD", "etcdctl", "endpoint", "health"] + interval: 30s + timeout: 20s + retries: 3 + networks: + - milvus + + minio: + container_name: milvus-minio + image: minio/minio:RELEASE.2023-03-20T20-16-18Z + profiles: + - milvus + environment: + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin} + volumes: + - ./volumes/milvus/minio:/minio_data + command: minio server /minio_data --console-address ":9001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + networks: + - milvus + + milvus-standalone: + container_name: milvus-standalone + image: milvusdb/milvus:v2.6.3 + profiles: + - milvus + command: ["milvus", "run", "standalone"] + environment: + ETCD_ENDPOINTS: ${ETCD_ENDPOINTS:-etcd:2379} + MINIO_ADDRESS: ${MINIO_ADDRESS:-minio:9000} + common.security.authorizationEnabled: ${MILVUS_AUTHORIZATION_ENABLED:-true} + volumes: + - ./volumes/milvus/milvus:/var/lib/milvus + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"] + interval: 30s + start_period: 90s + timeout: 20s + retries: 3 + depends_on: + - etcd + - minio + ports: + - 19530:19530 + - 9091:9091 + networks: + - milvus + + # Opensearch vector database + opensearch: + container_name: opensearch + image: opensearchproject/opensearch:latest + profiles: + - opensearch + environment: + discovery.type: ${OPENSEARCH_DISCOVERY_TYPE:-single-node} + bootstrap.memory_lock: ${OPENSEARCH_BOOTSTRAP_MEMORY_LOCK:-true} + OPENSEARCH_JAVA_OPTS: -Xms${OPENSEARCH_JAVA_OPTS_MIN:-512m} -Xmx${OPENSEARCH_JAVA_OPTS_MAX:-1024m} + OPENSEARCH_INITIAL_ADMIN_PASSWORD: ${OPENSEARCH_INITIAL_ADMIN_PASSWORD:-Qazwsxedc!@#123} + ulimits: + memlock: + soft: ${OPENSEARCH_MEMLOCK_SOFT:--1} + hard: ${OPENSEARCH_MEMLOCK_HARD:--1} + nofile: + soft: ${OPENSEARCH_NOFILE_SOFT:-65536} + hard: ${OPENSEARCH_NOFILE_HARD:-65536} + volumes: + - ./volumes/opensearch/data:/usr/share/opensearch/data + networks: + - opensearch-net + + opensearch-dashboards: + container_name: opensearch-dashboards + image: opensearchproject/opensearch-dashboards:latest + profiles: + - opensearch + environment: + OPENSEARCH_HOSTS: '["https://opensearch:9200"]' + volumes: + - ./volumes/opensearch/opensearch_dashboards.yml:/usr/share/opensearch-dashboards/config/opensearch_dashboards.yml + networks: + - opensearch-net + depends_on: + - opensearch + + # opengauss vector database. + opengauss: + image: opengauss/opengauss:7.0.0-RC1 + profiles: + - opengauss + privileged: true + restart: always + environment: + GS_USERNAME: ${OPENGAUSS_USER:-postgres} + GS_PASSWORD: ${OPENGAUSS_PASSWORD:-Dify@123} + GS_PORT: ${OPENGAUSS_PORT:-6600} + GS_DB: ${OPENGAUSS_DATABASE:-dify} + volumes: + - ./volumes/opengauss/data:/var/lib/opengauss/data + healthcheck: + test: ["CMD-SHELL", "netstat -lntp | grep tcp6 > /dev/null 2>&1"] + interval: 10s + timeout: 10s + retries: 10 + ports: + - ${OPENGAUSS_PORT:-6600}:${OPENGAUSS_PORT:-6600} + + # MyScale vector database + myscale: + container_name: myscale + image: myscale/myscaledb:1.6.4 + profiles: + - myscale + restart: always + tty: true + volumes: + - ./volumes/myscale/data:/var/lib/clickhouse + - ./volumes/myscale/log:/var/log/clickhouse-server + - ./volumes/myscale/config/users.d/custom_users_config.xml:/etc/clickhouse-server/users.d/custom_users_config.xml + ports: + - ${MYSCALE_PORT:-8123}:${MYSCALE_PORT:-8123} + + # Matrixone vector store. + matrixone: + hostname: matrixone + image: matrixorigin/matrixone:2.1.1 + profiles: + - matrixone + restart: always + volumes: + - ./volumes/matrixone/data:/mo-data + ports: + - ${MATRIXONE_PORT:-6001}:${MATRIXONE_PORT:-6001} + + # https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html + # https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-prod-prerequisites + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.14.3 + container_name: elasticsearch + profiles: + - elasticsearch + - elasticsearch-ja + restart: always + volumes: + - ./elasticsearch/docker-entrypoint.sh:/docker-entrypoint-mount.sh + - dify_es01_data:/usr/share/elasticsearch/data + environment: + ELASTIC_PASSWORD: ${ELASTICSEARCH_PASSWORD:-elastic} + VECTOR_STORE: ${VECTOR_STORE:-} + cluster.name: dify-es-cluster + node.name: dify-es0 + discovery.type: single-node + xpack.license.self_generated.type: basic + xpack.security.enabled: "true" + xpack.security.enrollment.enabled: "false" + xpack.security.http.ssl.enabled: "false" + ports: + - ${ELASTICSEARCH_PORT:-9200}:9200 + deploy: + resources: + limits: + memory: 2g + entrypoint: ["sh", "-c", "sh /docker-entrypoint-mount.sh"] + healthcheck: + test: + ["CMD", "curl", "-s", "http://localhost:9200/_cluster/health?pretty"] + interval: 30s + timeout: 10s + retries: 50 + + # https://www.elastic.co/guide/en/kibana/current/docker.html + # https://www.elastic.co/guide/en/kibana/current/settings.html + kibana: + image: docker.elastic.co/kibana/kibana:8.14.3 + container_name: kibana + profiles: + - elasticsearch + depends_on: + - elasticsearch + restart: always + environment: + XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY: d1a66dfd-c4d3-4a0a-8290-2abcb83ab3aa + NO_PROXY: localhost,127.0.0.1,elasticsearch,kibana + XPACK_SECURITY_ENABLED: "true" + XPACK_SECURITY_ENROLLMENT_ENABLED: "false" + XPACK_SECURITY_HTTP_SSL_ENABLED: "false" + XPACK_FLEET_ISAIRGAPPED: "true" + I18N_LOCALE: zh-CN + SERVER_PORT: "5601" + ELASTICSEARCH_HOSTS: http://elasticsearch:9200 + ports: + - ${KIBANA_PORT:-5601}:5601 + healthcheck: + test: ["CMD-SHELL", "curl -s http://localhost:5601 >/dev/null || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + + # unstructured . + # (if used, you need to set ETL_TYPE to Unstructured in the api & worker service.) + unstructured: + image: downloads.unstructured.io/unstructured-io/unstructured-api:latest + profiles: + - unstructured + restart: always + volumes: + - ./volumes/unstructured:/app/data + +networks: + # create a network between sandbox, api and ssrf_proxy, and can not access outside. + ssrf_proxy_network: + driver: bridge + internal: true + milvus: + driver: bridge + opensearch-net: + driver: bridge + internal: true + +volumes: + oradata: + dify_es01_data: diff --git a/480T/dify/docker-compose.middleware.yaml b/480T/dify/docker-compose.middleware.yaml new file mode 100755 index 0000000..01f8fbd --- /dev/null +++ b/480T/dify/docker-compose.middleware.yaml @@ -0,0 +1,260 @@ +services: + # The postgres database. + db_postgres: + image: postgres:15-alpine + profiles: + - "" + - postgresql + restart: always + env_file: + - ./middleware.env + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD:-difyai123456} + POSTGRES_DB: ${DB_DATABASE:-dify} + PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata} + command: > + postgres -c 'max_connections=${POSTGRES_MAX_CONNECTIONS:-100}' + -c 'shared_buffers=${POSTGRES_SHARED_BUFFERS:-128MB}' + -c 'work_mem=${POSTGRES_WORK_MEM:-4MB}' + -c 'maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-64MB}' + -c 'effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-4096MB}' + -c 'statement_timeout=${POSTGRES_STATEMENT_TIMEOUT:-0}' + -c 'idle_in_transaction_session_timeout=${POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT:-0}' + volumes: + - ${PGDATA_HOST_VOLUME:-./volumes/db/data}:/var/lib/postgresql/data + ports: + - "${EXPOSE_POSTGRES_PORT:-5432}:5432" + healthcheck: + test: + [ + "CMD", + "pg_isready", + "-h", + "db_postgres", + "-U", + "${DB_USERNAME:-postgres}", + "-d", + "${DB_DATABASE:-dify}", + ] + interval: 1s + timeout: 3s + retries: 30 + + db_mysql: + image: mysql:8.0 + profiles: + - mysql + restart: always + env_file: + - ./middleware.env + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-difyai123456} + MYSQL_DATABASE: ${DB_DATABASE:-dify} + command: > + --max_connections=${MYSQL_MAX_CONNECTIONS:-1000} + --innodb_buffer_pool_size=${MYSQL_INNODB_BUFFER_POOL_SIZE:-512M} + --innodb_log_file_size=${MYSQL_INNODB_LOG_FILE_SIZE:-128M} + --innodb_flush_log_at_trx_commit=${MYSQL_INNODB_FLUSH_LOG_AT_TRX_COMMIT:-2} + volumes: + - ${MYSQL_HOST_VOLUME:-./volumes/mysql/data}:/var/lib/mysql + ports: + - "${EXPOSE_MYSQL_PORT:-3306}:3306" + # mysqladmin ping passes during mysql:8.0's TCP-listening stage even while + # the server is still finalising init, leading to "Lost connection during + # query" on the first real query. Verify with a real SELECT instead. + healthcheck: + test: + [ + "CMD", + "mysql", + "-h", + "127.0.0.1", + "-uroot", + "-p${DB_PASSWORD:-difyai123456}", + "-e", + "SELECT 1", + ] + interval: 1s + timeout: 3s + retries: 30 + start_period: 20s + + # The redis cache. + redis: + image: redis:6-alpine + restart: always + env_file: + - ./middleware.env + environment: + REDISCLI_AUTH: ${REDIS_PASSWORD:-difyai123456} + volumes: + # Mount the redis data directory to the container. + - ${REDIS_HOST_VOLUME:-./volumes/redis/data}:/data + # Set the redis password when startup redis server. + command: redis-server --requirepass ${REDIS_PASSWORD:-difyai123456} + ports: + - "${EXPOSE_REDIS_PORT:-6379}:6379" + healthcheck: + test: + [ + "CMD-SHELL", + "redis-cli -a ${REDIS_PASSWORD:-difyai123456} ping | grep -q PONG", + ] + + # The DifySandbox + sandbox: + image: langgenius/dify-sandbox:0.2.15 + restart: always + env_file: + - ./middleware.env + environment: + # The DifySandbox configurations + # Make sure you are changing this key for your deployment with a strong key. + # You can generate a strong key using `openssl rand -base64 42`. + API_KEY: ${SANDBOX_API_KEY:-dify-sandbox} + GIN_MODE: ${SANDBOX_GIN_MODE:-release} + WORKER_TIMEOUT: ${SANDBOX_WORKER_TIMEOUT:-15} + ENABLE_NETWORK: ${SANDBOX_ENABLE_NETWORK:-true} + HTTP_PROXY: ${SANDBOX_HTTP_PROXY:-http://ssrf_proxy:3128} + HTTPS_PROXY: ${SANDBOX_HTTPS_PROXY:-http://ssrf_proxy:3128} + SANDBOX_PORT: ${SANDBOX_PORT:-8194} + PIP_MIRROR_URL: ${PIP_MIRROR_URL:-} + volumes: + - ./volumes/sandbox/dependencies:/dependencies + - ./volumes/sandbox/conf:/conf + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8194/health"] + networks: + - ssrf_proxy_network + + # plugin daemon + plugin_daemon: + image: langgenius/dify-plugin-daemon:0.6.3-local + restart: always + env_file: + - ./middleware.env + extra_hosts: + - "host.docker.internal:host-gateway" + environment: + # Use the shared environment variables. + LOG_OUTPUT_FORMAT: ${LOG_OUTPUT_FORMAT:-text} + DB_DATABASE: ${DB_PLUGIN_DATABASE:-dify_plugin} + REDIS_HOST: ${REDIS_HOST:-redis} + REDIS_PORT: ${REDIS_PORT:-6379} + REDIS_PASSWORD: ${REDIS_PASSWORD:-difyai123456} + SERVER_PORT: ${PLUGIN_DAEMON_PORT:-5002} + SERVER_KEY: ${PLUGIN_DAEMON_KEY:-lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi} + MAX_PLUGIN_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} + PPROF_ENABLED: ${PLUGIN_PPROF_ENABLED:-false} + DIFY_INNER_API_URL: ${PLUGIN_DIFY_INNER_API_URL:-http://host.docker.internal:5001} + DIFY_INNER_API_KEY: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} + PLUGIN_REMOTE_INSTALLING_HOST: ${PLUGIN_DEBUGGING_HOST:-0.0.0.0} + PLUGIN_REMOTE_INSTALLING_PORT: ${PLUGIN_DEBUGGING_PORT:-5003} + PLUGIN_WORKING_PATH: ${PLUGIN_WORKING_PATH:-/app/storage/cwd} + PYTHON_ENV_INIT_TIMEOUT: ${PLUGIN_PYTHON_ENV_INIT_TIMEOUT:-120} + PLUGIN_MAX_EXECUTION_TIMEOUT: ${PLUGIN_MAX_EXECUTION_TIMEOUT:-600} + PIP_MIRROR_URL: ${PIP_MIRROR_URL:-} + PLUGIN_STORAGE_TYPE: ${PLUGIN_STORAGE_TYPE:-local} + PLUGIN_STORAGE_LOCAL_ROOT: ${PLUGIN_STORAGE_LOCAL_ROOT:-/app/storage} + PLUGIN_INSTALLED_PATH: ${PLUGIN_INSTALLED_PATH:-plugin} + PLUGIN_PACKAGE_CACHE_PATH: ${PLUGIN_PACKAGE_CACHE_PATH:-plugin_packages} + PLUGIN_MEDIA_CACHE_PATH: ${PLUGIN_MEDIA_CACHE_PATH:-assets} + PLUGIN_STORAGE_OSS_BUCKET: ${PLUGIN_STORAGE_OSS_BUCKET:-} + S3_USE_AWS: ${PLUGIN_S3_USE_AWS:-false} + S3_USE_AWS_MANAGED_IAM: ${PLUGIN_S3_USE_AWS_MANAGED_IAM:-false} + S3_ENDPOINT: ${PLUGIN_S3_ENDPOINT:-} + S3_USE_PATH_STYLE: ${PLUGIN_S3_USE_PATH_STYLE:-false} + AWS_ACCESS_KEY: ${PLUGIN_AWS_ACCESS_KEY:-} + AWS_SECRET_KEY: ${PLUGIN_AWS_SECRET_KEY:-} + AWS_REGION: ${PLUGIN_AWS_REGION:-} + AZURE_BLOB_STORAGE_CONNECTION_STRING: ${PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING:-} + AZURE_BLOB_STORAGE_CONTAINER_NAME: ${PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME:-} + TENCENT_COS_SECRET_KEY: ${PLUGIN_TENCENT_COS_SECRET_KEY:-} + TENCENT_COS_SECRET_ID: ${PLUGIN_TENCENT_COS_SECRET_ID:-} + TENCENT_COS_REGION: ${PLUGIN_TENCENT_COS_REGION:-} + ALIYUN_OSS_REGION: ${PLUGIN_ALIYUN_OSS_REGION:-} + ALIYUN_OSS_ENDPOINT: ${PLUGIN_ALIYUN_OSS_ENDPOINT:-} + ALIYUN_OSS_ACCESS_KEY_ID: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID:-} + ALIYUN_OSS_ACCESS_KEY_SECRET: ${PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET:-} + ALIYUN_OSS_AUTH_VERSION: ${PLUGIN_ALIYUN_OSS_AUTH_VERSION:-v4} + ALIYUN_OSS_PATH: ${PLUGIN_ALIYUN_OSS_PATH:-} + VOLCENGINE_TOS_ENDPOINT: ${PLUGIN_VOLCENGINE_TOS_ENDPOINT:-} + VOLCENGINE_TOS_ACCESS_KEY: ${PLUGIN_VOLCENGINE_TOS_ACCESS_KEY:-} + VOLCENGINE_TOS_SECRET_KEY: ${PLUGIN_VOLCENGINE_TOS_SECRET_KEY:-} + VOLCENGINE_TOS_REGION: ${PLUGIN_VOLCENGINE_TOS_REGION:-} + THIRD_PARTY_SIGNATURE_VERIFICATION_ENABLED: true + THIRD_PARTY_SIGNATURE_VERIFICATION_PUBLIC_KEYS: /app/keys/publickey.pem + FORCE_VERIFYING_SIGNATURE: false + ports: + - "${EXPOSE_PLUGIN_DAEMON_PORT:-5002}:${PLUGIN_DAEMON_PORT:-5002}" + - "${EXPOSE_PLUGIN_DEBUGGING_PORT:-5003}:${PLUGIN_DEBUGGING_PORT:-5003}" + volumes: + - ./volumes/plugin_daemon:/app/storage + + # ssrf_proxy server + # for more information, please refer to + # https://docs.dify.ai/learn-more/faq/install-faq#18-why-is-ssrf-proxy-needed%3F + ssrf_proxy: + image: ubuntu/squid:latest + restart: always + volumes: + - ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template + - ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh + entrypoint: + [ + "sh", + "-c", + "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh", + ] + env_file: + - ./middleware.env + environment: + # pls clearly modify the squid env vars to fit your network environment. + HTTP_PORT: ${SSRF_HTTP_PORT:-3128} + COREDUMP_DIR: ${SSRF_COREDUMP_DIR:-/var/spool/squid} + SSRF_PROXY_ALLOW_PRIVATE_IPS: ${SSRF_PROXY_ALLOW_PRIVATE_IPS:-} + SSRF_PROXY_ALLOW_PRIVATE_DOMAINS: ${SSRF_PROXY_ALLOW_PRIVATE_DOMAINS:-} + SSRF_SANDBOX_PROXY_PORT: ${SSRF_SANDBOX_PROXY_PORT:-8194} + SSRF_SANDBOX_PROXY_HOST: ${SSRF_SANDBOX_PROXY_HOST:-sandbox} + ports: + - "${EXPOSE_SSRF_PROXY_PORT:-3128}:${SSRF_HTTP_PORT:-3128}" + - "${EXPOSE_SANDBOX_PORT:-8194}:${SSRF_SANDBOX_PROXY_PORT:-8194}" + networks: + - ssrf_proxy_network + - default + + # The Weaviate vector store. + weaviate: + image: semitechnologies/weaviate:1.27.0 + profiles: + - "" + - weaviate + restart: always + volumes: + # Mount the Weaviate data directory to the container. + - ${WEAVIATE_HOST_VOLUME:-./volumes/weaviate}:/var/lib/weaviate + env_file: + - ./middleware.env + environment: + # The Weaviate configurations + # You can refer to the [Weaviate](https://weaviate.io/developers/weaviate/config-refs/env-vars) documentation for more information. + PERSISTENCE_DATA_PATH: ${WEAVIATE_PERSISTENCE_DATA_PATH:-/var/lib/weaviate} + QUERY_DEFAULTS_LIMIT: ${WEAVIATE_QUERY_DEFAULTS_LIMIT:-25} + AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: ${WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED:-false} + DEFAULT_VECTORIZER_MODULE: ${WEAVIATE_DEFAULT_VECTORIZER_MODULE:-none} + CLUSTER_HOSTNAME: ${WEAVIATE_CLUSTER_HOSTNAME:-node1} + AUTHENTICATION_APIKEY_ENABLED: ${WEAVIATE_AUTHENTICATION_APIKEY_ENABLED:-true} + AUTHENTICATION_APIKEY_ALLOWED_KEYS: ${WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS:-WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih} + AUTHENTICATION_APIKEY_USERS: ${WEAVIATE_AUTHENTICATION_APIKEY_USERS:-hello@dify.ai} + AUTHORIZATION_ADMINLIST_ENABLED: ${WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED:-true} + AUTHORIZATION_ADMINLIST_USERS: ${WEAVIATE_AUTHORIZATION_ADMINLIST_USERS:-hello@dify.ai} + DISABLE_TELEMETRY: ${WEAVIATE_DISABLE_TELEMETRY:-false} + ports: + - "${EXPOSE_WEAVIATE_PORT:-8080}:8080" + - "${EXPOSE_WEAVIATE_GRPC_PORT:-50051}:50051" + +networks: + # create a network between sandbox, api and ssrf_proxy, and can not access outside. + ssrf_proxy_network: + driver: bridge + internal: true diff --git a/480T/dify/docker-compose.png b/480T/dify/docker-compose.png new file mode 100755 index 0000000000000000000000000000000000000000..015d450236ae236f8708db8b857631d2b2b56615 GIT binary patch literal 174447 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
    *zfCX_T!oQW+i07=IM4i z`yKBE4ZW%Fr_8p$|9L}SPmcGpkKQYHA>Q~_<|5sf$x2IwI=um1rNd%klJT=%C-PTV?8`xRrs+Synt8sfxcTdK!B8dX}c$Z;@Ju4POK|D zGr4v4o?3lIAh_>n2xUmJ<$&kO2J-4)z)d7?xxWShP%=!I=B}*)^_N_s`J74*3`IXf zMhvU#M_Gfwyu=Ts=4Qu_F!xhF-qNs^CO^zQ-Y-AicIjRf>~xmaeWWyd8Vvs#WItEd z<6}R^LU-M+xTWUAy3W-Y{D)6x); {mheQ`J>-n> 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�@^u%6f6-S!2c}e)_9{ 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 literal 0 HcmV?d00001 diff --git a/480T/dify/docker-compose.pytest.ports.yaml b/480T/dify/docker-compose.pytest.ports.yaml new file mode 100755 index 0000000..b26e82e --- /dev/null +++ b/480T/dify/docker-compose.pytest.ports.yaml @@ -0,0 +1,30 @@ +services: + weaviate: + ports: + - "${EXPOSE_WEAVIATE_PORT:-8080}:8080" + - "${EXPOSE_WEAVIATE_GRPC_PORT:-50051}:50051" + + qdrant: + ports: + - "${EXPOSE_QDRANT_PORT:-6333}:6333" + + pgvector: + ports: + - "${EXPOSE_PGVECTOR_PORT:-5433}:5432" + + pgvecto-rs: + ports: + - "${EXPOSE_PGVECTO_RS_PORT:-5431}:5432" + + chroma: + ports: + - "${EXPOSE_CHROMA_PORT:-8000}:8000" + + couchbase-server: + ports: + - "${EXPOSE_COUCHBASE_WEB_PORT_RANGE:-8091-8096}:8091-8096" + - "${EXPOSE_COUCHBASE_DATA_PORT:-11210}:11210" + + opensearch: + ports: + - "${EXPOSE_OPENSEARCH_PORT:-9200}:9200" diff --git a/480T/dify/elasticsearch/docker-entrypoint.sh b/480T/dify/elasticsearch/docker-entrypoint.sh new file mode 100755 index 0000000..6669aec --- /dev/null +++ b/480T/dify/elasticsearch/docker-entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e + +if [ "${VECTOR_STORE}" = "elasticsearch-ja" ]; then + # Check if the ICU tokenizer plugin is installed + if ! /usr/share/elasticsearch/bin/elasticsearch-plugin list | grep -q analysis-icu; then + printf '%s\n' "Installing the ICU tokenizer plugin" + if ! /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-icu; then + printf '%s\n' "Failed to install the ICU tokenizer plugin" + exit 1 + fi + fi + # Check if the Japanese language analyzer plugin is installed + if ! /usr/share/elasticsearch/bin/elasticsearch-plugin list | grep -q analysis-kuromoji; then + printf '%s\n' "Installing the Japanese language analyzer plugin" + if ! /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji; then + printf '%s\n' "Failed to install the Japanese language analyzer plugin" + exit 1 + fi + fi +fi + +# Run the original entrypoint script +exec /bin/tini -- /usr/local/bin/docker-entrypoint.sh diff --git a/480T/dify/envs/core-services/api.env.example b/480T/dify/envs/core-services/api.env.example new file mode 100755 index 0000000..538c554 --- /dev/null +++ b/480T/dify/envs/core-services/api.env.example @@ -0,0 +1,18 @@ +# ------------------------------ +# Api Configuration +# ------------------------------ + +MODE=api +SENTRY_DSN= +SENTRY_TRACES_SAMPLE_RATE=1.0 +SENTRY_PROFILES_SAMPLE_RATE=1.0 +PLUGIN_REMOTE_INSTALL_HOST=localhost +PLUGIN_REMOTE_INSTALL_PORT=5003 +PLUGIN_MAX_PACKAGE_SIZE=52428800 +PLUGIN_DAEMON_TIMEOUT=600.0 +INNER_API_KEY_FOR_PLUGIN=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 +KNOWLEDGE_FS_ENABLED=${KNOWLEDGE_FS_ENABLED:-false} +KNOWLEDGE_FS_BASE_URL= +KNOWLEDGE_FS_JWT_SECRET= +KNOWLEDGE_FS_SSE_READ_TIMEOUT_SECONDS=300 +KNOWLEDGE_FS_TIMEOUT_SECONDS=10 diff --git a/480T/dify/envs/core-services/dify-agent.env.example b/480T/dify/envs/core-services/dify-agent.env.example new file mode 100755 index 0000000..f2d7155 --- /dev/null +++ b/480T/dify/envs/core-services/dify-agent.env.example @@ -0,0 +1,36 @@ +# ------------------------------ +# Dify Agent Backend Configuration +# ------------------------------ + +AGENT_BACKEND_BASE_URL=http://agent_backend:5050 +AGENT_BACKEND_STREAM_READ_TIMEOUT_SECONDS=30 +AGENT_BACKEND_STREAM_MAX_RECONNECTS=3 +AGENT_BACKEND_RUN_TIMEOUT_SECONDS=1200 + +# Leave empty to derive from REDIS_PASSWORD in Docker Compose. +DIFY_AGENT_REDIS_URL= +DIFY_AGENT_REDIS_PREFIX=dify-agent +DIFY_AGENT_SHUTDOWN_GRACE_SECONDS=30 +DIFY_AGENT_RUN_RETENTION_SECONDS=259200 + +# Leave empty to derive from PLUGIN_DAEMON_URL and PLUGIN_DAEMON_KEY in Docker Compose. +DIFY_AGENT_PLUGIN_DAEMON_URL= +DIFY_AGENT_PLUGIN_DAEMON_API_KEY= + +# Leave empty to derive from PLUGIN_DIFY_INNER_API_URL and PLUGIN_DIFY_INNER_API_KEY in Docker Compose. +# DIFY_AGENT_INNER_API_KEY must match API/worker INNER_API_KEY_FOR_PLUGIN, not INNER_API_KEY. +DIFY_AGENT_INNER_API_URL= +DIFY_AGENT_INNER_API_KEY= + +DIFY_AGENT_SHELLCTL_ENTRYPOINT=http://local_sandbox:5004 +DIFY_AGENT_SHELLCTL_AUTH_TOKEN= +DIFY_AGENT_STUB_API_BASE_URL=http://agent_backend:5050/agent-stub +# This is security-sensitive: it derives the JWE encryption key for Agent Stub bearer tokens. +# Replace this development default in production. +# Generate one with: python -c 'import secrets; print(secrets.token_urlsafe(32))' +DIFY_AGENT_SERVER_SECRET_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY + +# JSON array of regex patterns to redact from shell output shown to the agent. +# The JWE token value is always redacted regardless of this setting. +# Example: DIFY_AGENT_SHELL_REDACT_PATTERNS=["sk-[A-Za-z0-9]+","ghp_[A-Za-z0-9]{36}"] +DIFY_AGENT_SHELL_REDACT_PATTERNS= diff --git a/480T/dify/envs/core-services/local-sandbox.env.example b/480T/dify/envs/core-services/local-sandbox.env.example new file mode 100755 index 0000000..97eed34 --- /dev/null +++ b/480T/dify/envs/core-services/local-sandbox.env.example @@ -0,0 +1,16 @@ +# ------------------------------ +# Dify Agent Local Sandbox Configuration +# ------------------------------ + +# Set to false to disable Landlock path isolation (default: true). +SHELLCTL_ENABLE_PATH_ISOLATION=true + +# The paths below override the built-in defaults when set, even to an empty value. +# Leave them commented out to keep the defaults; empty read-only or device path +# lists can prevent commands from running. +# Comma-separated additional read-write directories (default: none beyond $HOME). +# SHELLCTL_LANDLOCK_RW_PATHS= +# Comma-separated read-only and executable directories (default: /usr,/bin,/sbin,/lib,/lib64,/etc,/proc,/opt/dify-agent-tools,/opt/homebrew,/snap). +# SHELLCTL_LANDLOCK_RO_PATHS= +# Comma-separated read-write device files (default: /dev/null,/dev/zero,/dev/urandom,/dev/random,/dev/tty). +# SHELLCTL_LANDLOCK_RW_DEV_PATHS= diff --git a/480T/dify/envs/core-services/plugin-daemon.env.example b/480T/dify/envs/core-services/plugin-daemon.env.example new file mode 100755 index 0000000..c3b1bef --- /dev/null +++ b/480T/dify/envs/core-services/plugin-daemon.env.example @@ -0,0 +1,23 @@ +# ------------------------------ +# Plugin Daemon Configuration +# ------------------------------ + +DB_PLUGIN_DATABASE=dify_plugin +PLUGIN_DAEMON_URL=http://plugin_daemon:5002 +PLUGIN_PPROF_ENABLED=false +PLUGIN_DIFY_INNER_API_URL=http://api:5001 +FORCE_VERIFYING_SIGNATURE=true +PLUGIN_STDIO_BUFFER_SIZE=1024 +PLUGIN_STDIO_MAX_BUFFER_SIZE=5242880 +PLUGIN_PYTHON_ENV_INIT_TIMEOUT=120 +PLUGIN_MAX_EXECUTION_TIMEOUT=600 +PLUGIN_DEBUGGING_HOST=0.0.0.0 +PLUGIN_DEBUGGING_PORT=5003 +PLUGIN_DAEMON_KEY=lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi +PLUGIN_DIFY_INNER_API_KEY=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 +PLUGIN_DAEMON_PORT=5002 +CELERY_WORKER_CLASS= +PLUGIN_STORAGE_TYPE=local +PLUGIN_STORAGE_LOCAL_ROOT=/app/storage +PLUGIN_WORKING_PATH=/app/storage/cwd +PLUGIN_STORAGE_OSS_BUCKET= diff --git a/480T/dify/envs/core-services/sandbox.env.example b/480T/dify/envs/core-services/sandbox.env.example new file mode 100755 index 0000000..5d4ee66 --- /dev/null +++ b/480T/dify/envs/core-services/sandbox.env.example @@ -0,0 +1,17 @@ +# ------------------------------ +# Sandbox Configuration +# ------------------------------ + +SANDBOX_HTTP_PROXY=http://ssrf_proxy:3128 +SANDBOX_HTTPS_PROXY=http://ssrf_proxy:3128 +SANDBOX_PORT=8194 +PIP_MIRROR_URL= +SANDBOX_API_KEY=dify-sandbox +SANDBOX_GIN_MODE=release +SANDBOX_WORKER_TIMEOUT=15 +SANDBOX_ENABLE_NETWORK=true +SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD=21 +SANDBOX_EXPIRED_RECORDS_CLEAN_BATCH_SIZE=1000 +SANDBOX_EXPIRED_RECORDS_CLEAN_BATCH_MAX_INTERVAL=200 +SANDBOX_EXPIRED_RECORDS_RETENTION_DAYS=30 +SANDBOX_EXPIRED_RECORDS_CLEAN_TASK_LOCK_TTL=90000 diff --git a/480T/dify/envs/core-services/shared.env.example b/480T/dify/envs/core-services/shared.env.example new file mode 100755 index 0000000..5fe6ab9 --- /dev/null +++ b/480T/dify/envs/core-services/shared.env.example @@ -0,0 +1,513 @@ +# ------------------------------ +# Shared API/Worker Configuration +# ------------------------------ + +CONSOLE_WEB_URL= +SERVICE_API_URL= +TRIGGER_URL=http://localhost +APP_WEB_URL= +# FILES_URL= +# INTERNAL_FILES_URL= +LANG=C.UTF-8 +LC_ALL=C.UTF-8 +PYTHONIOENCODING=utf-8 +UV_CACHE_DIR=/tmp/.uv-cache +CHECK_UPDATE_URL=https://updates.dify.ai +OPENAI_API_BASE=https://api.openai.com/v1 +MIGRATION_ENABLED=true +FILES_ACCESS_TIMEOUT=300 +# System Features +MARKETPLACE_ENABLED=true +ENABLE_EMAIL_CODE_LOGIN=false +ENABLE_EMAIL_PASSWORD_LOGIN=true +ENABLE_SOCIAL_OAUTH_LOGIN=false +# Remove `collaboration` from COMPOSE_PROFILES to stop the dedicated websocket service. +ENABLE_COLLABORATION_MODE=true +ALLOW_REGISTER=false +ALLOW_CREATE_WORKSPACE=false +ENABLE_CHANGE_EMAIL=true +ENABLE_TRIAL_APP=false +ENABLE_EXPLORE_BANNER=false +ENABLE_LEARN_APP=true +ENABLE_STEP_BY_STEP_TOUR=false +RBAC_ENABLED=false +CELERY_BROKER_URL=redis://:difyai123456@redis:6379/1 +CELERY_TASK_ANNOTATIONS=null +AZURE_BLOB_ACCOUNT_URL=https://.blob.core.windows.net +SUPABASE_URL=your-server-url +TIDB_ON_QDRANT_URL=http://127.0.0.1 +TIDB_ON_QDRANT_API_KEY=dify +TIDB_API_URL=http://127.0.0.1 +TIDB_IAM_API_URL=http://127.0.0.1 +TIDB_REGION=regions/aws-us-east-1 +TIDB_PROJECT_ID=dify +TIDB_SPEND_LIMIT=100 +TENCENT_VECTOR_DB_URL=http://127.0.0.1 +TENCENT_VECTOR_DB_API_KEY=dify +LINDORM_URL=http://localhost:30070 +LINDORM_USERNAME=admin +UPSTASH_VECTOR_URL=https://xxx-vector.upstash.io +UPLOAD_FILE_SIZE_LIMIT=15 +UPLOAD_FILE_BATCH_LIMIT=5 +UPLOAD_FILE_EXTENSION_BLACKLIST= +SINGLE_CHUNK_ATTACHMENT_LIMIT=10 +IMAGE_FILE_BATCH_LIMIT=10 +ATTACHMENT_IMAGE_FILE_SIZE_LIMIT=2 +ATTACHMENT_IMAGE_DOWNLOAD_TIMEOUT=60 +ETL_TYPE=dify +UNSTRUCTURED_API_URL= +MULTIMODAL_SEND_FORMAT=base64 +UPLOAD_IMAGE_FILE_SIZE_LIMIT=10 +UPLOAD_VIDEO_FILE_SIZE_LIMIT=100 +UPLOAD_AUDIO_FILE_SIZE_LIMIT=50 +API_SENTRY_DSN= +API_SENTRY_TRACES_SAMPLE_RATE=1.0 +API_SENTRY_PROFILES_SAMPLE_RATE=1.0 +WEB_SENTRY_DSN= +PLUGIN_SENTRY_ENABLED=false +PLUGIN_SENTRY_DSN= +NOTION_INTEGRATION_TYPE=public +RESEND_API_URL=https://api.resend.com +SSRF_PROXY_HTTP_URL=http://ssrf_proxy:3128 +SSRF_PROXY_HTTPS_URL=http://ssrf_proxy:3128 +PGDATA=/var/lib/postgresql/data/pgdata +PLUGIN_MAX_PACKAGE_SIZE=52428800 +PLUGIN_MODEL_SCHEMA_CACHE_TTL=3600 +PLUGIN_MODEL_PROVIDERS_CACHE_TTL=86400 +# Comma-separated marketplace plugin IDs whose latest versions are installed for newly registered users. +# Example: langgenius/openai,langgenius/gemini +NEW_USER_DEFAULT_PLUGIN_IDS= +# Comma-separated model_type:provider:model entries assigned after default plugins finish installing. +# Example: llm:langgenius/openai/openai:gpt-4o-mini,text-embedding:langgenius/openai/openai:text-embedding-3-small +NEW_USER_DEFAULT_MODELS= +ENDPOINT_URL_TEMPLATE=http://localhost/e/{hook_id} +LOG_LEVEL=INFO +LOG_OUTPUT_FORMAT=text +LOG_FILE=/app/logs/server.log +LOG_FILE_MAX_SIZE=20 +LOG_FILE_BACKUP_COUNT=5 +LOG_DATEFORMAT=%Y-%m-%d %H:%M:%S +LOG_TZ=UTC +DEBUG=false +FLASK_DEBUG=false +ENABLE_REQUEST_LOGGING=False +OPS_TRACE_RETRYABLE_DISPATCH_MAX_RETRIES=60 +OPS_TRACE_RETRYABLE_DISPATCH_DELAY_SECONDS=5 +WORKFLOW_LOG_CLEANUP_ENABLED=false +WORKFLOW_LOG_RETENTION_DAYS=30 +WORKFLOW_LOG_CLEANUP_BATCH_SIZE=100 +WORKFLOW_LOG_CLEANUP_SPECIFIC_WORKFLOW_IDS= +EXPOSE_PLUGIN_DEBUGGING_HOST=localhost +EXPOSE_PLUGIN_DEBUGGING_PORT=5003 +DEPLOY_ENV=PRODUCTION +EDITION=SELF_HOSTED +ENTERPRISE_ENABLED=false +ACCESS_TOKEN_EXPIRE_MINUTES=60 +REFRESH_TOKEN_EXPIRE_DAYS=30 +APP_DEFAULT_ACTIVE_REQUESTS=0 +APP_MAX_ACTIVE_REQUESTS=0 +APP_MAX_EXECUTION_TIME=1200 +DIFY_BIND_ADDRESS=0.0.0.0 +DIFY_PORT=5001 +SERVER_WORKER_AMOUNT=1 +SERVER_WORKER_CLASS=gevent +SERVER_WORKER_CONNECTIONS=10 +API_WEBSOCKET_WORKER_AMOUNT=1 +API_WEBSOCKET_WORKER_CLASS=geventwebsocket.gunicorn.workers.GeventWebSocketWorker +API_WEBSOCKET_WORKER_CONNECTIONS=1000 +API_WEBSOCKET_GUNICORN_TIMEOUT=360 +CELERY_SENTINEL_PASSWORD= +S3_ACCESS_KEY= +S3_SECRET_KEY= +ARCHIVE_STORAGE_ACCESS_KEY= +ARCHIVE_STORAGE_SECRET_KEY= +AZURE_BLOB_ACCOUNT_KEY=difyai +ALIYUN_OSS_ACCESS_KEY=your-access-key +ALIYUN_OSS_SECRET_KEY=your-secret-key +TENCENT_COS_SECRET_KEY=your-secret-key +TENCENT_COS_SECRET_ID=your-secret-id +OCI_ACCESS_KEY=your-access-key +OCI_SECRET_KEY=your-secret-key +HUAWEI_OBS_SECRET_KEY=your-secret-key +HUAWEI_OBS_ACCESS_KEY=your-access-key +VOLCENGINE_TOS_SECRET_KEY=your-secret-key +VOLCENGINE_TOS_ACCESS_KEY=your-access-key +BAIDU_OBS_SECRET_KEY=your-secret-key +BAIDU_OBS_ACCESS_KEY=your-access-key +SUPABASE_API_KEY=your-access-key +ALIBABACLOUD_MYSQL_PASSWORD=difyai123456 +RELYT_PASSWORD=difyai123456 +LINDORM_PASSWORD=admin +LINDORM_USING_UGC=True +LINDORM_QUERY_TIMEOUT=1 +HUAWEI_CLOUD_PASSWORD=admin +UPSTASH_VECTOR_TOKEN=dify +TABLESTORE_ACCESS_KEY_ID=xxx +TABLESTORE_ACCESS_KEY_SECRET=xxx +TABLESTORE_NORMALIZE_FULLTEXT_BM25_SCORE=false +CLICKZETTA_PASSWORD= +CLICKZETTA_INSTANCE= +CLICKZETTA_SERVICE=api.clickzetta.com +CLICKZETTA_WORKSPACE=quick_start +CLICKZETTA_VCLUSTER=default_ap +CLICKZETTA_SCHEMA=dify +CLICKZETTA_BATCH_SIZE=100 +CLICKZETTA_ENABLE_INVERTED_INDEX=true +CLICKZETTA_ANALYZER_TYPE=chinese +CLICKZETTA_ANALYZER_MODE=smart +UNSTRUCTURED_API_KEY= +SCARF_NO_ANALYTICS=true +PLUGIN_BASED_TOKEN_COUNTING_ENABLED=false +NOTION_CLIENT_SECRET= +NOTION_CLIENT_ID= +NOTION_INTERNAL_SECRET= +MAIL_TYPE=resend +MAIL_DEFAULT_SEND_FROM= +RESEND_API_KEY=your-resend-api-key +SMTP_SERVER= +SMTP_PORT=465 +SMTP_USERNAME= +SMTP_PASSWORD= +SMTP_USE_TLS=true +SMTP_OPPORTUNISTIC_TLS=false +SMTP_LOCAL_HOSTNAME= +SENDGRID_API_KEY= +INVITE_EXPIRY_HOURS=72 +RESET_PASSWORD_TOKEN_EXPIRY_MINUTES=5 +EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES=5 +CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES=5 +OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES=5 +CODE_EXECUTION_ENDPOINT=http://sandbox:8194 +CODE_EXECUTION_API_KEY=dify-sandbox +CODE_EXECUTION_SSL_VERIFY=True +CODE_EXECUTION_POOL_MAX_CONNECTIONS=100 +CODE_EXECUTION_POOL_MAX_KEEPALIVE_CONNECTIONS=20 +CODE_EXECUTION_POOL_KEEPALIVE_EXPIRY=5.0 +CODE_MAX_NUMBER=9223372036854775807 +CODE_MIN_NUMBER=-9223372036854775808 +CODE_MAX_DEPTH=5 +CODE_MAX_PRECISION=20 +CODE_MAX_STRING_LENGTH=400000 +CODE_MAX_STRING_ARRAY_LENGTH=30 +CODE_MAX_OBJECT_ARRAY_LENGTH=30 +CODE_MAX_NUMBER_ARRAY_LENGTH=1000 +CODE_EXECUTION_CONNECT_TIMEOUT=10 +CODE_EXECUTION_READ_TIMEOUT=60 +CODE_EXECUTION_WRITE_TIMEOUT=10 +TEMPLATE_TRANSFORM_MAX_LENGTH=400000 +WORKFLOW_MAX_EXECUTION_STEPS=500 +WORKFLOW_MAX_EXECUTION_TIME=1200 +WORKFLOW_CALL_MAX_DEPTH=5 +MAX_VARIABLE_SIZE=204800 +WORKFLOW_GENERATOR_NODE_BUILDER_MAX_WORKERS=6 +WORKFLOW_GENERATION_TIMEOUT_MS=180000 +WORKFLOW_FILE_UPLOAD_LIMIT=10 +GRAPH_ENGINE_MIN_WORKERS=3 +GRAPH_ENGINE_MAX_WORKERS=10 +GRAPH_ENGINE_SCALE_UP_THRESHOLD=3 +GRAPH_ENGINE_SCALE_DOWN_IDLE_TIME=5.0 +ALIYUN_SLS_ACCESS_KEY_ID= +ALIYUN_SLS_ACCESS_KEY_SECRET= +WEBHOOK_REQUEST_BODY_MAX_SIZE=10485760 +RESPECT_XFORWARD_HEADERS_ENABLED=false +SSRF_HTTP_PORT=3128 +SSRF_COREDUMP_DIR=/var/spool/squid +SSRF_DEFAULT_TIME_OUT=5 +SSRF_DEFAULT_CONNECT_TIME_OUT=5 +SSRF_DEFAULT_READ_TIME_OUT=5 +SSRF_DEFAULT_WRITE_TIME_OUT=5 +SSRF_POOL_MAX_CONNECTIONS=100 +SSRF_POOL_MAX_KEEPALIVE_CONNECTIONS=20 +SSRF_POOL_KEEPALIVE_EXPIRY=5.0 +PLUGIN_AWS_ACCESS_KEY= +PLUGIN_AWS_SECRET_KEY= +PLUGIN_AWS_REGION= +PLUGIN_TENCENT_COS_SECRET_KEY= +PLUGIN_TENCENT_COS_SECRET_ID= +PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID= +PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET= +PLUGIN_VOLCENGINE_TOS_ACCESS_KEY= +PLUGIN_VOLCENGINE_TOS_SECRET_KEY= +OTLP_API_KEY= +OTEL_EXPORTER_OTLP_PROTOCOL= +OTEL_EXPORTER_TYPE=otlp +OTEL_SAMPLING_RATE=0.1 +OTEL_BATCH_EXPORT_SCHEDULE_DELAY=5000 +OTEL_MAX_QUEUE_SIZE=2048 +OTEL_MAX_EXPORT_BATCH_SIZE=512 +OTEL_METRIC_EXPORT_INTERVAL=60000 +OTEL_BATCH_EXPORT_TIMEOUT=10000 +OTEL_METRIC_EXPORT_TIMEOUT=30000 +QUEUE_MONITOR_THRESHOLD=200 +QUEUE_MONITOR_ALERT_EMAILS= +QUEUE_MONITOR_INTERVAL=30 +SWAGGER_UI_ENABLED=false +SWAGGER_UI_PATH=/swagger-ui.html +OPENAPI_ENABLED=false +OPENAPI_CORS_ALLOW_ORIGINS= +OPENAPI_KNOWN_CLIENT_IDS=difyctl +OPENAPI_RATE_LIMIT_PER_TOKEN=60 +DEVICE_FLOW_APPROVE_RATE_LIMIT_PER_HOUR=10 +ENABLE_OAUTH_BEARER=false +DSL_EXPORT_ENCRYPT_DATASET_ID=true +DATASET_MAX_SEGMENTS_PER_REQUEST=0 +ENABLE_CLEAN_EMBEDDING_CACHE_TASK=false +ENABLE_CLEAN_UNUSED_DATASETS_TASK=false +ENABLE_CREATE_TIDB_SERVERLESS_TASK=false +ENABLE_UPDATE_TIDB_SERVERLESS_STATUS_TASK=false +ENABLE_CLEAN_MESSAGES=false +ENABLE_WORKFLOW_RUN_CLEANUP_TASK=false +ENABLE_MAIL_CLEAN_DOCUMENT_NOTIFY_TASK=false +ENABLE_DATASETS_QUEUE_MONITOR=false +ENABLE_CHECK_UPGRADABLE_PLUGIN_TASK=true +ENABLE_WORKFLOW_SCHEDULE_POLLER_TASK=true +WORKFLOW_SCHEDULE_POLLER_INTERVAL=1 +WORKFLOW_SCHEDULE_POLLER_BATCH_SIZE=100 +WORKFLOW_SCHEDULE_MAX_DISPATCH_PER_TICK=0 +TENANT_ISOLATED_TASK_CONCURRENCY=1 +ANNOTATION_IMPORT_FILE_SIZE_LIMIT=2 +ANNOTATION_IMPORT_MAX_RECORDS=10000 +ANNOTATION_IMPORT_MIN_RECORDS=1 +ANNOTATION_IMPORT_RATE_LIMIT_PER_MINUTE=5 +ANNOTATION_IMPORT_RATE_LIMIT_PER_HOUR=20 +ANNOTATION_IMPORT_MAX_CONCURRENT=5 +CREATORS_PLATFORM_FEATURES_ENABLED=true +CREATORS_PLATFORM_API_URL=https://creators.dify.ai +CREATORS_PLATFORM_OAUTH_CLIENT_ID= +TIDB_VECTOR_DATABASE=dify +ALIBABACLOUD_MYSQL_HOST=127.0.0.1 +ALIBABACLOUD_MYSQL_PORT=3306 +ALIBABACLOUD_MYSQL_USER=root +ALIBABACLOUD_MYSQL_DATABASE=dify +ALIBABACLOUD_MYSQL_MAX_CONNECTION=5 +ALIBABACLOUD_MYSQL_HNSW_M=6 +RELYT_DATABASE=postgres +TENCENT_VECTOR_DB_DATABASE=dify +BAIDU_VECTOR_DB_DATABASE=dify +EXPOSE_PLUGIN_DAEMON_PORT=5002 +GUNICORN_TIMEOUT=360 +CELERY_WORKER_AMOUNT= +CELERY_AUTO_SCALE=false +CELERY_MAX_WORKERS= +CELERY_MIN_WORKERS= +API_TOOL_DEFAULT_CONNECT_TIMEOUT=10 +API_TOOL_DEFAULT_READ_TIMEOUT=60 +CELERY_BACKEND=redis +CELERY_USE_SENTINEL=false +CELERY_SENTINEL_MASTER_NAME= +CELERY_SENTINEL_SOCKET_TIMEOUT=0.1 +WEB_API_CORS_ALLOW_ORIGINS=* +CONSOLE_CORS_ALLOW_ORIGINS=* +COOKIE_DOMAIN= +OPENDAL_SCHEME=fs +OPENDAL_FS_ROOT=storage +CLICKZETTA_VOLUME_TYPE=user +CLICKZETTA_VOLUME_NAME= +CLICKZETTA_VOLUME_TABLE_PREFIX=dataset_ +CLICKZETTA_VOLUME_DIFY_PREFIX=dify_km +S3_ENDPOINT= +S3_REGION=us-east-1 +S3_BUCKET_NAME=difyai +S3_ADDRESS_STYLE=auto +S3_USE_AWS_MANAGED_IAM=false +ARCHIVE_STORAGE_ENABLED=false +ARCHIVE_STORAGE_ENDPOINT= +ARCHIVE_STORAGE_ARCHIVE_BUCKET= +ARCHIVE_STORAGE_EXPORT_BUCKET= +ARCHIVE_STORAGE_REGION=auto +AZURE_BLOB_ACCOUNT_NAME=difyai +AZURE_BLOB_CONTAINER_NAME=difyai-container +GOOGLE_STORAGE_BUCKET_NAME=your-bucket-name +GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64= +ALIYUN_OSS_BUCKET_NAME=your-bucket-name +ALIYUN_OSS_ENDPOINT=https://oss-ap-southeast-1-internal.aliyuncs.com +ALIYUN_OSS_REGION=ap-southeast-1 +ALIYUN_OSS_AUTH_VERSION=v4 +ALIYUN_OSS_PATH=your-path +ALIYUN_CLOUDBOX_ID=your-cloudbox-id +TENCENT_COS_BUCKET_NAME=your-bucket-name +TENCENT_COS_REGION=your-region +TENCENT_COS_SCHEME=your-scheme +TENCENT_COS_CUSTOM_DOMAIN=your-custom-domain +OCI_ENDPOINT=https://your-object-storage-namespace.compat.objectstorage.us-ashburn-1.oraclecloud.com +OCI_BUCKET_NAME=your-bucket-name +OCI_REGION=us-ashburn-1 +HUAWEI_OBS_BUCKET_NAME=your-bucket-name +HUAWEI_OBS_SERVER=your-server-url +HUAWEI_OBS_PATH_STYLE=false +VOLCENGINE_TOS_BUCKET_NAME=your-bucket-name +VOLCENGINE_TOS_ENDPOINT=your-server-url +VOLCENGINE_TOS_REGION=your-region +BAIDU_OBS_BUCKET_NAME=your-bucket-name +BAIDU_OBS_ENDPOINT=your-server-url +SUPABASE_BUCKET_NAME=your-bucket-name +TENCENT_VECTOR_DB_TIMEOUT=30 +TENCENT_VECTOR_DB_USERNAME=dify +TENCENT_VECTOR_DB_SHARD=1 +TENCENT_VECTOR_DB_REPLICAS=2 +TENCENT_VECTOR_DB_ENABLE_HYBRID_SEARCH=false +BAIDU_VECTOR_DB_ENDPOINT=http://127.0.0.1:5287 +BAIDU_VECTOR_DB_CONNECTION_TIMEOUT_MS=30000 +BAIDU_VECTOR_DB_ACCOUNT=root +BAIDU_VECTOR_DB_API_KEY=dify +BAIDU_VECTOR_DB_SHARD=1 +BAIDU_VECTOR_DB_REPLICAS=3 +BAIDU_VECTOR_DB_INVERTED_INDEX_ANALYZER=DEFAULT_ANALYZER +BAIDU_VECTOR_DB_INVERTED_INDEX_PARSER_MODE=COARSE_MODE +BAIDU_VECTOR_DB_AUTO_BUILD_ROW_COUNT_INCREMENT=500 +BAIDU_VECTOR_DB_AUTO_BUILD_ROW_COUNT_INCREMENT_RATIO=0.05 +BAIDU_VECTOR_DB_REBUILD_INDEX_TIMEOUT_IN_SECONDS=300 +HUAWEI_CLOUD_HOSTS=https://127.0.0.1:9200 +HUAWEI_CLOUD_USER=admin +WORKFLOW_NODE_EXECUTION_STORAGE=rdbms +CORE_WORKFLOW_EXECUTION_REPOSITORY=core.repositories.sqlalchemy_workflow_execution_repository.SQLAlchemyWorkflowExecutionRepository +CORE_WORKFLOW_NODE_EXECUTION_REPOSITORY=core.repositories.sqlalchemy_workflow_node_execution_repository.SQLAlchemyWorkflowNodeExecutionRepository +API_WORKFLOW_RUN_REPOSITORY=repositories.sqlalchemy_api_workflow_run_repository.DifyAPISQLAlchemyWorkflowRunRepository +API_WORKFLOW_NODE_EXECUTION_REPOSITORY=repositories.sqlalchemy_api_workflow_node_execution_repository.DifyAPISQLAlchemyWorkflowNodeExecutionRepository +ALIYUN_SLS_ENDPOINT= +ALIYUN_SLS_REGION= +ALIYUN_SLS_PROJECT_NAME= +ALIYUN_SLS_LOGSTORE_TTL=365 +LOGSTORE_DUAL_WRITE_ENABLED=false +LOGSTORE_DUAL_READ_ENABLED=true +LOGSTORE_ENABLE_PUT_GRAPH_FIELD=true +HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760 +HTTP_REQUEST_NODE_MAX_TEXT_SIZE=1048576 +HTTP_REQUEST_NODE_SSL_VERIFY=True +HTTP_REQUEST_MAX_CONNECT_TIMEOUT=10 +HTTP_REQUEST_MAX_READ_TIMEOUT=600 +HTTP_REQUEST_MAX_WRITE_TIMEOUT=600 +PLUGIN_INSTALLED_PATH=plugin +PLUGIN_PACKAGE_CACHE_PATH=plugin_packages +PLUGIN_MEDIA_CACHE_PATH=assets +PLUGIN_S3_USE_AWS=false +PLUGIN_S3_USE_AWS_MANAGED_IAM=false +PLUGIN_S3_ENDPOINT= +PLUGIN_S3_USE_PATH_STYLE=false +PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME= +PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING= +PLUGIN_TENCENT_COS_REGION= +PLUGIN_ALIYUN_OSS_REGION= +PLUGIN_ALIYUN_OSS_ENDPOINT= +PLUGIN_ALIYUN_OSS_AUTH_VERSION=v4 +PLUGIN_ALIYUN_OSS_PATH= +PLUGIN_VOLCENGINE_TOS_ENDPOINT= +PLUGIN_VOLCENGINE_TOS_REGION= +ENABLE_OTEL=false +OTLP_TRACE_ENDPOINT= +OTLP_METRIC_ENDPOINT= +# Prefix used to create collection name in vector database +OTLP_BASE_ENDPOINT=http://localhost:4318 +WEAVIATE_GRPC_ENDPOINT=grpc://weaviate:50051 +ANALYTICDB_KEY_ID=your-ak +ANALYTICDB_KEY_SECRET=your-sk +ANALYTICDB_REGION_ID=cn-hangzhou +ANALYTICDB_INSTANCE_ID=gp-ab123456 +ANALYTICDB_ACCOUNT=testaccount +ANALYTICDB_PASSWORD=testpassword +ANALYTICDB_NAMESPACE=dify +ANALYTICDB_NAMESPACE_PASSWORD=difypassword +ANALYTICDB_HOST=gp-test.aliyuncs.com +ANALYTICDB_PORT=5432 +ANALYTICDB_MIN_CONNECTION=1 +ANALYTICDB_MAX_CONNECTION=5 +TIDB_VECTOR_HOST=tidb +TIDB_VECTOR_PORT=4000 +TIDB_VECTOR_USER= +TIDB_VECTOR_PASSWORD= +TIDB_ON_QDRANT_CLIENT_TIMEOUT=20 +TIDB_ON_QDRANT_GRPC_ENABLED=false +TIDB_ON_QDRANT_GRPC_PORT=6334 +TIDB_PUBLIC_KEY=dify +TIDB_PRIVATE_KEY=dify +RELYT_HOST=db +RELYT_PORT=5432 +RELYT_USER=postgres +VIKINGDB_ACCESS_KEY=your-ak +VIKINGDB_SECRET_KEY=your-sk +VIKINGDB_REGION=cn-shanghai +VIKINGDB_HOST=api-vikingdb.xxx.volces.com +VIKINGDB_SCHEME=http +VIKINGDB_CONNECTION_TIMEOUT=30 +VIKINGDB_SOCKET_TIMEOUT=30 +TABLESTORE_ENDPOINT=https://instance-name.cn-hangzhou.ots.aliyuncs.com +TABLESTORE_INSTANCE_NAME=instance-name +CLICKZETTA_USERNAME= +CLICKZETTA_VECTOR_DISTANCE_FUNCTION=cosine_distance +COMPOSE_PROFILES=${VECTOR_STORE:-weaviate},${DB_TYPE:-postgresql},collaboration +EXPOSE_NGINX_PORT=80 +EXPOSE_NGINX_SSL_PORT=443 +POSITION_TOOL_PINS= +POSITION_TOOL_INCLUDES= +POSITION_TOOL_EXCLUDES= +POSITION_PROVIDER_PINS= +POSITION_PROVIDER_INCLUDES= +POSITION_PROVIDER_EXCLUDES= +CREATE_TIDB_SERVICE_JOB_ENABLED=false +MAX_SUBMIT_COUNT=100 + +# Vector Store Configuration +STORAGE_TYPE=opendal +VECTOR_STORE=weaviate +VECTOR_INDEX_NAME_PREFIX=Vector_index +WEAVIATE_ENDPOINT=http://weaviate:8080 +WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih +WEAVIATE_TOKENIZATION=word +OCEANBASE_VECTOR_HOST=oceanbase +OCEANBASE_VECTOR_PORT=2881 +OCEANBASE_VECTOR_USER=root@test +OCEANBASE_VECTOR_PASSWORD=difyai123456 +OCEANBASE_VECTOR_DATABASE=test +OCEANBASE_ENABLE_HYBRID_SEARCH=false +OCEANBASE_FULLTEXT_PARSER=ik +SEEKDB_MEMORY_LIMIT=2G +QDRANT_URL=http://qdrant:6333 +QDRANT_API_KEY=difyai123456 +QDRANT_CLIENT_TIMEOUT=20 +QDRANT_GRPC_ENABLED=false +QDRANT_GRPC_PORT=6334 +QDRANT_REPLICATION_FACTOR=1 +MILVUS_URI=http://host.docker.internal:19530 +MILVUS_TOKEN= +MILVUS_USER= +MILVUS_PASSWORD= +MILVUS_ANALYZER_PARAMS= +PGVECTOR_HOST=pgvector +PGVECTOR_PORT=5432 +PGVECTOR_USER=postgres +PGVECTOR_PASSWORD=difyai123456 +PGVECTOR_DATABASE=dify +PGVECTOR_MIN_CONNECTION=1 +PGVECTOR_MAX_CONNECTION=5 +PGVECTOR_PG_BIGM=false +PGVECTOR_PG_BIGM_VERSION=1.2-20240606 + +# Hologres Configuration +HOLOGRES_HOST= +HOLOGRES_PORT=80 +HOLOGRES_DATABASE= +HOLOGRES_ACCESS_KEY_ID= +HOLOGRES_ACCESS_KEY_SECRET= +HOLOGRES_SCHEMA=public +HOLOGRES_TOKENIZER=jieba +HOLOGRES_DISTANCE_METHOD=Cosine +HOLOGRES_BASE_QUANTIZATION_TYPE=rabitq +HOLOGRES_MAX_DEGREE=64 +HOLOGRES_EF_CONSTRUCTION=400 + +# Milvus API Configuration +MILVUS_DATABASE= +MILVUS_ENABLE_HYBRID_SEARCH=False + +# Human Input Task Configuration +ENABLE_HUMAN_INPUT_TIMEOUT_TASK=true +HUMAN_INPUT_TIMEOUT_TASK_INTERVAL=1 + +# Nacos remote settings source HTTP timeouts (seconds). +# Bound how long requests to the Nacos endpoint wait before failing, so a slow or +# unresponsive Nacos server cannot stall API startup or token refresh. +DIFY_ENV_NACOS_REQUEST_TIMEOUT=10.0 +DIFY_ENV_NACOS_CONNECT_TIMEOUT=3.0 + +# uv cache dir +UV_CACHE_DIR=/tmp/uv_cache diff --git a/480T/dify/envs/core-services/web.env.example b/480T/dify/envs/core-services/web.env.example new file mode 100755 index 0000000..5c952f4 --- /dev/null +++ b/480T/dify/envs/core-services/web.env.example @@ -0,0 +1,32 @@ +# ------------------------------ +# Web Configuration +# ------------------------------ + +CONSOLE_API_URL= +APP_API_URL= +SENTRY_DSN= +NEXT_PUBLIC_SOCKET_URL=ws://localhost +EXPERIMENTAL_ENABLE_VINEXT=false +LOOP_NODE_MAX_COUNT=100 +MAX_TOOLS_NUM=10 +MAX_PARALLEL_LIMIT=10 +MAX_ITERATIONS_NUM=99 +TEXT_GENERATION_TIMEOUT_MS=60000 +ALLOW_INLINE_STYLES=false +ALLOW_UNSAFE_DATA_SCHEME=false +MAX_TREE_DEPTH=50 +MARKETPLACE_API_URL=https://marketplace.dify.ai +INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000 +ALLOW_EMBED=false +AMPLITUDE_API_KEY= +COOKIEYES_SITE_KEY= +ENABLE_WEBSITE_JINAREADER=true +ENABLE_WEBSITE_FIRECRAWL=true +ENABLE_WEBSITE_WATERCRAWL=true +NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX=false +NEXT_PUBLIC_ENABLE_FEATURE_PREVIEW=true +NEXT_PUBLIC_ENABLE_AGENT_V2=true +NEXT_PUBLIC_COOKIE_DOMAIN= +NEXT_PUBLIC_BATCH_CONCURRENCY=5 +CSP_WHITELIST= +TOP_K_MAX_VALUE=10 diff --git a/480T/dify/envs/core-services/worker-beat.env.example b/480T/dify/envs/core-services/worker-beat.env.example new file mode 100755 index 0000000..380fe02 --- /dev/null +++ b/480T/dify/envs/core-services/worker-beat.env.example @@ -0,0 +1,8 @@ +# ------------------------------ +# Worker Beat Configuration +# ------------------------------ + +MODE=beat +COMPOSE_WORKER_HEALTHCHECK_DISABLED=true +COMPOSE_WORKER_HEALTHCHECK_INTERVAL=30s +COMPOSE_WORKER_HEALTHCHECK_TIMEOUT=30s diff --git a/480T/dify/envs/core-services/worker.env.example b/480T/dify/envs/core-services/worker.env.example new file mode 100755 index 0000000..58cf4ea --- /dev/null +++ b/480T/dify/envs/core-services/worker.env.example @@ -0,0 +1,13 @@ +# ------------------------------ +# Worker Configuration +# ------------------------------ + +MODE=worker +SENTRY_DSN= +SENTRY_TRACES_SAMPLE_RATE=1.0 +SENTRY_PROFILES_SAMPLE_RATE=1.0 +PLUGIN_MAX_PACKAGE_SIZE=52428800 +INNER_API_KEY_FOR_PLUGIN=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 +COMPOSE_WORKER_HEALTHCHECK_DISABLED=true +COMPOSE_WORKER_HEALTHCHECK_INTERVAL=30s +COMPOSE_WORKER_HEALTHCHECK_TIMEOUT=30s diff --git a/480T/dify/envs/databases/db-mysql.env.example b/480T/dify/envs/databases/db-mysql.env.example new file mode 100755 index 0000000..b3ea680 --- /dev/null +++ b/480T/dify/envs/databases/db-mysql.env.example @@ -0,0 +1,9 @@ +# ------------------------------ +# Db Mysql Configuration +# ------------------------------ + +MYSQL_INNODB_LOG_FILE_SIZE=128M +MYSQL_INNODB_FLUSH_LOG_AT_TRX_COMMIT=2 +MYSQL_MAX_CONNECTIONS=1000 +MYSQL_INNODB_BUFFER_POOL_SIZE=512M +MYSQL_HOST_VOLUME=./volumes/mysql/data diff --git a/480T/dify/envs/databases/db-postgres.env.example b/480T/dify/envs/databases/db-postgres.env.example new file mode 100755 index 0000000..14cefb6 --- /dev/null +++ b/480T/dify/envs/databases/db-postgres.env.example @@ -0,0 +1,26 @@ +# ------------------------------ +# Db Postgres Configuration +# ------------------------------ + +PGDATA=/var/lib/postgresql/data/pgdata +DB_TYPE=postgresql +DB_USERNAME=postgres +DB_PASSWORD=difyai123456 +DB_HOST=db_postgres +DB_PORT=5432 +DB_DATABASE=dify +SQLALCHEMY_POOL_SIZE=30 +SQLALCHEMY_MAX_OVERFLOW=10 +SQLALCHEMY_POOL_RECYCLE=3600 +SQLALCHEMY_ECHO=false +SQLALCHEMY_POOL_PRE_PING=false +SQLALCHEMY_POOL_USE_LIFO=false +SQLALCHEMY_POOL_TIMEOUT=30 +SQLALCHEMY_POOL_RESET_ON_RETURN=rollback +POSTGRES_MAX_CONNECTIONS=100 +POSTGRES_SHARED_BUFFERS=128MB +POSTGRES_WORK_MEM=4MB +POSTGRES_MAINTENANCE_WORK_MEM=64MB +POSTGRES_EFFECTIVE_CACHE_SIZE=4096MB +POSTGRES_STATEMENT_TIMEOUT=0 +POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT=0 diff --git a/480T/dify/envs/databases/redis.env.example b/480T/dify/envs/databases/redis.env.example new file mode 100755 index 0000000..56be6f4 --- /dev/null +++ b/480T/dify/envs/databases/redis.env.example @@ -0,0 +1,39 @@ +# ------------------------------ +# Redis Configuration +# ------------------------------ + +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_USERNAME= +REDIS_PASSWORD=difyai123456 +REDIS_USE_SSL=false +REDIS_SSL_CERT_REQS=CERT_NONE +REDIS_SSL_CA_CERTS= +REDIS_SSL_CERTFILE= +REDIS_SSL_KEYFILE= +REDIS_DB=0 +REDIS_KEY_PREFIX= +REDIS_MAX_CONNECTIONS= +REDIS_USE_SENTINEL=false +REDIS_SENTINELS= +REDIS_SENTINEL_SERVICE_NAME= +REDIS_SENTINEL_USERNAME= +REDIS_SENTINEL_PASSWORD= +REDIS_SENTINEL_SOCKET_TIMEOUT=0.1 +REDIS_USE_CLUSTERS=false +REDIS_CLUSTERS= +REDIS_CLUSTERS_PASSWORD= +REDIS_RETRY_RETRIES=3 +REDIS_RETRY_BACKOFF_BASE=1.0 +REDIS_RETRY_BACKOFF_CAP=10.0 +REDIS_SOCKET_TIMEOUT=5.0 +REDIS_SOCKET_CONNECT_TIMEOUT=5.0 +REDIS_HEALTH_CHECK_INTERVAL=30 +REDIS_KEEPALIVE=true +REDIS_KEEPALIVE_IDLE=30 +REDIS_KEEPALIVE_INTERVAL=10 +REDIS_KEEPALIVE_COUNT=10 +EVENT_BUS_REDIS_URL= +EVENT_BUS_REDIS_CHANNEL_TYPE=pubsub +EVENT_BUS_REDIS_USE_CLUSTERS=false +BROKER_USE_SSL=false diff --git a/480T/dify/envs/infrastructure/certbot.env.example b/480T/dify/envs/infrastructure/certbot.env.example new file mode 100755 index 0000000..c654fbe --- /dev/null +++ b/480T/dify/envs/infrastructure/certbot.env.example @@ -0,0 +1,7 @@ +# ------------------------------ +# Certbot Configuration +# ------------------------------ + +CERTBOT_EMAIL=your_email@example.com +CERTBOT_DOMAIN=your_domain.com +CERTBOT_OPTIONS= diff --git a/480T/dify/envs/infrastructure/etcd.env.example b/480T/dify/envs/infrastructure/etcd.env.example new file mode 100755 index 0000000..4dca266 --- /dev/null +++ b/480T/dify/envs/infrastructure/etcd.env.example @@ -0,0 +1,4 @@ +# ------------------------------ +# Etcd Configuration +# ------------------------------ + diff --git a/480T/dify/envs/infrastructure/milvus-standalone.env.example b/480T/dify/envs/infrastructure/milvus-standalone.env.example new file mode 100755 index 0000000..7e87ed2 --- /dev/null +++ b/480T/dify/envs/infrastructure/milvus-standalone.env.example @@ -0,0 +1,4 @@ +# ------------------------------ +# Milvus Standalone Configuration +# ------------------------------ + diff --git a/480T/dify/envs/infrastructure/minio.env.example b/480T/dify/envs/infrastructure/minio.env.example new file mode 100755 index 0000000..7c8e1fa --- /dev/null +++ b/480T/dify/envs/infrastructure/minio.env.example @@ -0,0 +1,4 @@ +# ------------------------------ +# Minio Configuration +# ------------------------------ + diff --git a/480T/dify/envs/infrastructure/nginx.env.example b/480T/dify/envs/infrastructure/nginx.env.example new file mode 100755 index 0000000..fcb369a --- /dev/null +++ b/480T/dify/envs/infrastructure/nginx.env.example @@ -0,0 +1,18 @@ +# ------------------------------ +# Nginx Configuration +# ------------------------------ + +NGINX_SERVER_NAME=_ +NGINX_HTTPS_ENABLED=false +NGINX_PORT=80 +NGINX_SSL_PORT=443 +NGINX_SSL_CERT_FILENAME=dify.crt +NGINX_SSL_CERT_KEY_FILENAME=dify.key +NGINX_SSL_PROTOCOLS=TLSv1.2 TLSv1.3 +NGINX_WORKER_PROCESSES=auto +NGINX_CLIENT_MAX_BODY_SIZE=100M +NGINX_KEEPALIVE_TIMEOUT=65 +NGINX_PROXY_READ_TIMEOUT=3600s +NGINX_PROXY_SEND_TIMEOUT=3600s +NGINX_ENABLE_CERTBOT_CHALLENGE=false +NGINX_SOCKET_IO_UPSTREAM=api_websocket:5001 diff --git a/480T/dify/envs/infrastructure/ssrf-proxy.env.example b/480T/dify/envs/infrastructure/ssrf-proxy.env.example new file mode 100755 index 0000000..3624bd9 --- /dev/null +++ b/480T/dify/envs/infrastructure/ssrf-proxy.env.example @@ -0,0 +1,17 @@ +# ------------------------------ +# Ssrf Proxy Configuration +# ------------------------------ + +SSRF_PROXY_HTTP_URL=http://ssrf_proxy:3128 +SSRF_PROXY_HTTPS_URL=http://ssrf_proxy:3128 +SSRF_HTTP_PORT=3128 +SSRF_COREDUMP_DIR=/var/spool/squid +SSRF_PROXY_ALLOW_PRIVATE_IPS= +SSRF_PROXY_ALLOW_PRIVATE_DOMAINS= +SSRF_DEFAULT_TIME_OUT=5 +SSRF_DEFAULT_CONNECT_TIME_OUT=5 +SSRF_DEFAULT_READ_TIME_OUT=5 +SSRF_DEFAULT_WRITE_TIME_OUT=5 +SSRF_POOL_MAX_CONNECTIONS=100 +SSRF_POOL_MAX_KEEPALIVE_CONNECTIONS=20 +SSRF_POOL_KEEPALIVE_EXPIRY=5.0 diff --git a/480T/dify/envs/middleware.env.example b/480T/dify/envs/middleware.env.example new file mode 100755 index 0000000..3ff8139 --- /dev/null +++ b/480T/dify/envs/middleware.env.example @@ -0,0 +1,245 @@ +# ------------------------------ +# Environment Variables for db Service +# ------------------------------ +# Database Configuration +# Database type, supported values are `postgresql` and `mysql` +DB_TYPE=postgresql +# For MySQL, only `root` user is supported for now +DB_USERNAME=postgres +DB_PASSWORD=difyai123456 +DB_HOST=db_postgres +DB_PORT=5432 +DB_DATABASE=dify + +# PostgreSQL Configuration +# postgres data directory +PGDATA=/var/lib/postgresql/data/pgdata +PGDATA_HOST_VOLUME=./volumes/db/data + +# Maximum number of connections to the database +# Default is 100 +# +# Reference: https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-MAX-CONNECTIONS +POSTGRES_MAX_CONNECTIONS=100 + +# Sets the amount of shared memory used for postgres's shared buffers. +# Default is 128MB +# Recommended value: 25% of available memory +# Reference: https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-SHARED-BUFFERS +POSTGRES_SHARED_BUFFERS=128MB + +# Sets the amount of memory used by each database worker for working space. +# Default is 4MB +# +# Reference: https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-WORK-MEM +POSTGRES_WORK_MEM=4MB + +# Sets the amount of memory reserved for maintenance activities. +# Default is 64MB +# +# Reference: https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAINTENANCE-WORK-MEM +POSTGRES_MAINTENANCE_WORK_MEM=64MB + +# Sets the planner's assumption about the effective cache size. +# Default is 4096MB +# +# Reference: https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-EFFECTIVE-CACHE-SIZE +POSTGRES_EFFECTIVE_CACHE_SIZE=4096MB + +# Sets the maximum allowed duration of any statement before termination. +# Default is 0 (no timeout). +# +# Reference: https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STATEMENT-TIMEOUT +# A value of 0 prevents the server from timing out statements. +POSTGRES_STATEMENT_TIMEOUT=0 + +# Sets the maximum allowed duration of any idle in-transaction session before termination. +# Default is 0 (no timeout). +# +# Reference: https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT +# A value of 0 prevents the server from terminating idle sessions. +POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT=0 + +# MySQL Configuration +# MySQL data directory host volume +MYSQL_HOST_VOLUME=./volumes/mysql/data + +# MySQL Performance Configuration +# Maximum number of connections to MySQL +# Default is 1000 +MYSQL_MAX_CONNECTIONS=1000 + +# InnoDB buffer pool size +# Default is 512M +# Recommended value: 70-80% of available memory for dedicated MySQL server +# Reference: https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size +MYSQL_INNODB_BUFFER_POOL_SIZE=512M + +# InnoDB log file size +# Default is 128M +# Reference: https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_log_file_size +MYSQL_INNODB_LOG_FILE_SIZE=128M + +# InnoDB flush log at transaction commit +# Default is 2 (flush to OS cache, sync every second) +# Options: 0 (no flush), 1 (flush and sync), 2 (flush to OS cache) +# Reference: https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit +MYSQL_INNODB_FLUSH_LOG_AT_TRX_COMMIT=2 + +# ----------------------------- +# Environment Variables for redis Service +# ----------------------------- +REDIS_HOST_VOLUME=./volumes/redis/data +REDIS_PASSWORD=difyai123456 +# Optional: limit total Redis connections used by API/Worker (unset for default) +# Align with API's REDIS_MAX_CONNECTIONS in configs +REDIS_MAX_CONNECTIONS= + +# ------------------------------ +# Environment Variables for sandbox Service +# ------------------------------ +SANDBOX_API_KEY=dify-sandbox +SANDBOX_GIN_MODE=release +SANDBOX_WORKER_TIMEOUT=15 +SANDBOX_ENABLE_NETWORK=true +SANDBOX_HTTP_PROXY=http://ssrf_proxy:3128 +SANDBOX_HTTPS_PROXY=http://ssrf_proxy:3128 +SANDBOX_PORT=8194 + +# ------------------------------ +# Environment Variables for ssrf_proxy Service +# ------------------------------ +SSRF_HTTP_PORT=3128 +SSRF_COREDUMP_DIR=/var/spool/squid +SSRF_PROXY_ALLOW_PRIVATE_IPS= +SSRF_PROXY_ALLOW_PRIVATE_DOMAINS= +SSRF_SANDBOX_PROXY_PORT=8194 +SSRF_SANDBOX_PROXY_HOST=sandbox + +# ------------------------------ +# Environment Variables for weaviate Service +# ------------------------------ +WEAVIATE_QUERY_DEFAULTS_LIMIT=25 +WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true +WEAVIATE_DEFAULT_VECTORIZER_MODULE=none +WEAVIATE_CLUSTER_HOSTNAME=node1 +WEAVIATE_AUTHENTICATION_APIKEY_ENABLED=true +WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih +WEAVIATE_AUTHENTICATION_APIKEY_USERS=hello@dify.ai +WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED=true +WEAVIATE_AUTHORIZATION_ADMINLIST_USERS=hello@dify.ai +WEAVIATE_DISABLE_TELEMETRY=false +WEAVIATE_HOST_VOLUME=./volumes/weaviate + +# ------------------------------ +# Docker Compose profile configuration +# ------------------------------ +# Loaded automatically when running `docker compose --env-file middleware.env ...`. +# Controls which DB/vector services start, so no extra `--profile` flag is needed. +COMPOSE_PROFILES=${DB_TYPE:-postgresql},weaviate + +# ------------------------------ +# Docker Compose Service Expose Host Port Configurations +# ------------------------------ +EXPOSE_POSTGRES_PORT=5432 +EXPOSE_MYSQL_PORT=3306 +EXPOSE_REDIS_PORT=6379 +EXPOSE_SANDBOX_PORT=8194 +EXPOSE_SSRF_PROXY_PORT=3128 +EXPOSE_WEAVIATE_PORT=8080 + +# ------------------------------ +# Plugin Daemon Configuration +# ------------------------------ + +DB_PLUGIN_DATABASE=dify_plugin +EXPOSE_PLUGIN_DAEMON_PORT=5002 +PLUGIN_DAEMON_PORT=5002 +PLUGIN_DAEMON_KEY=lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi +PLUGIN_DAEMON_URL=http://host.docker.internal:5002 +PLUGIN_MAX_PACKAGE_SIZE=52428800 +PLUGIN_PPROF_ENABLED=false +PLUGIN_WORKING_PATH=/app/storage/cwd + +ENDPOINT_URL_TEMPLATE=http://localhost:5002/e/{hook_id} + +PLUGIN_DEBUGGING_PORT=5003 +PLUGIN_DEBUGGING_HOST=0.0.0.0 +EXPOSE_PLUGIN_DEBUGGING_HOST=localhost +EXPOSE_PLUGIN_DEBUGGING_PORT=5003 + +PLUGIN_DIFY_INNER_API_KEY=QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1 +PLUGIN_DIFY_INNER_API_URL=http://host.docker.internal:5001 + +MARKETPLACE_ENABLED=true +MARKETPLACE_API_URL=https://marketplace.dify.ai + +FORCE_VERIFYING_SIGNATURE=true + +PLUGIN_PYTHON_ENV_INIT_TIMEOUT=120 +PLUGIN_MAX_EXECUTION_TIMEOUT=600 +# PIP_MIRROR_URL=https://pypi.tuna.tsinghua.edu.cn/simple +PIP_MIRROR_URL= + +# https://github.com/langgenius/dify-plugin-daemon/blob/main/.env.example +# Plugin storage type, local aws_s3 tencent_cos azure_blob +PLUGIN_STORAGE_TYPE=local +PLUGIN_STORAGE_LOCAL_ROOT=/app/storage +PLUGIN_WORKING_PATH=/app/storage/cwd +PLUGIN_INSTALLED_PATH=plugin +PLUGIN_PACKAGE_CACHE_PATH=plugin_packages +PLUGIN_MEDIA_CACHE_PATH=assets +# Plugin oss bucket +PLUGIN_STORAGE_OSS_BUCKET= +# Plugin oss s3 credentials +PLUGIN_S3_USE_AWS_MANAGED_IAM=false +PLUGIN_S3_USE_AWS=false +PLUGIN_S3_ENDPOINT= +PLUGIN_S3_USE_PATH_STYLE=false +PLUGIN_AWS_ACCESS_KEY= +PLUGIN_AWS_SECRET_KEY= +PLUGIN_AWS_REGION= +# Plugin oss azure blob +PLUGIN_AZURE_BLOB_STORAGE_CONTAINER_NAME= +PLUGIN_AZURE_BLOB_STORAGE_CONNECTION_STRING= +# Plugin oss tencent cos +PLUGIN_TENCENT_COS_SECRET_KEY= +PLUGIN_TENCENT_COS_SECRET_ID= +PLUGIN_TENCENT_COS_REGION= +# Plugin oss aliyun oss +PLUGIN_ALIYUN_OSS_REGION= +PLUGIN_ALIYUN_OSS_ENDPOINT= +PLUGIN_ALIYUN_OSS_ACCESS_KEY_ID= +PLUGIN_ALIYUN_OSS_ACCESS_KEY_SECRET= +PLUGIN_ALIYUN_OSS_AUTH_VERSION=v4 +PLUGIN_ALIYUN_OSS_PATH= +# Plugin oss volcengine tos +PLUGIN_VOLCENGINE_TOS_ENDPOINT= +PLUGIN_VOLCENGINE_TOS_ACCESS_KEY= +PLUGIN_VOLCENGINE_TOS_SECRET_KEY= +PLUGIN_VOLCENGINE_TOS_REGION= + +# ------------------------------ +# Environment Variables for Aliyun SLS (Simple Log Service) +# ------------------------------ +# Aliyun SLS Access Key ID +ALIYUN_SLS_ACCESS_KEY_ID= +# Aliyun SLS Access Key Secret +ALIYUN_SLS_ACCESS_KEY_SECRET= +# Aliyun SLS Endpoint (e.g., cn-hangzhou.log.aliyuncs.com) +ALIYUN_SLS_ENDPOINT= +# Aliyun SLS Region (e.g., cn-hangzhou) +ALIYUN_SLS_REGION= +# Aliyun SLS Project Name +ALIYUN_SLS_PROJECT_NAME= +# Aliyun SLS Logstore TTL (default: 365 days, 3650 for permanent storage) +ALIYUN_SLS_LOGSTORE_TTL=365 +# Enable dual-write to both LogStore and SQL database (default: true) +LOGSTORE_DUAL_WRITE_ENABLED=true +# Enable dual-read fallback to SQL database when LogStore returns no results (default: true) +# Useful for migration scenarios where historical data exists only in SQL database +LOGSTORE_DUAL_READ_ENABLED=true +# Control flag for whether to write the `graph` field to LogStore. +# If LOGSTORE_ENABLE_PUT_GRAPH_FIELD is "true", write the full `graph` field; +# otherwise write an empty {} instead. Defaults to writing the `graph` field. +LOGSTORE_ENABLE_PUT_GRAPH_FIELD=true diff --git a/480T/dify/envs/security.env.example b/480T/dify/envs/security.env.example new file mode 100755 index 0000000..d7556d9 --- /dev/null +++ b/480T/dify/envs/security.env.example @@ -0,0 +1,41 @@ +# ------------------------------ +# Security Configuration +# ------------------------------ + +TIDB_ON_QDRANT_API_KEY=dify +TENCENT_VECTOR_DB_API_KEY=dify +ALIBABACLOUD_MYSQL_PASSWORD=difyai123456 +RELYT_PASSWORD=difyai123456 +LINDORM_PASSWORD=admin +HUAWEI_CLOUD_PASSWORD=admin +UPSTASH_VECTOR_TOKEN=dify +TABLESTORE_ACCESS_KEY_ID=xxx +TABLESTORE_ACCESS_KEY_SECRET=xxx +UNSTRUCTURED_API_KEY= +PLUGIN_BASED_TOKEN_COUNTING_ENABLED=false +NOTION_CLIENT_SECRET= +NOTION_INTERNAL_SECRET= +RESEND_API_KEY=your-resend-api-key +SMTP_PASSWORD= +SENDGRID_API_KEY= +RESET_PASSWORD_TOKEN_EXPIRY_MINUTES=5 +EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES=5 +CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES=5 +OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES=5 +CODE_EXECUTION_API_KEY=dify-sandbox +ALIYUN_SLS_ACCESS_KEY_ID= +ALIYUN_SLS_ACCESS_KEY_SECRET= +OTLP_API_KEY= +BAIDU_VECTOR_DB_API_KEY=dify +ANALYTICDB_KEY_ID=your-ak +ANALYTICDB_KEY_SECRET=your-sk +ANALYTICDB_PASSWORD=testpassword +ANALYTICDB_NAMESPACE_PASSWORD=difypassword +TIDB_VECTOR_PASSWORD= +TIDB_PUBLIC_KEY=dify +TIDB_PRIVATE_KEY=dify +VIKINGDB_ACCESS_KEY=your-ak +VIKINGDB_SECRET_KEY=your-sk +# Leave empty to auto-generate a persistent key in the storage directory. +SECRET_KEY= +INIT_PASSWORD= diff --git a/480T/dify/envs/vectorstores/chroma.env.example b/480T/dify/envs/vectorstores/chroma.env.example new file mode 100755 index 0000000..2a15375 --- /dev/null +++ b/480T/dify/envs/vectorstores/chroma.env.example @@ -0,0 +1,13 @@ +# ------------------------------ +# Chroma Configuration +# ------------------------------ + +CHROMA_DATABASE=default_database +CHROMA_AUTH_PROVIDER=chromadb.auth.token_authn.TokenAuthClientProvider +CHROMA_AUTH_CREDENTIALS= +CHROMA_HOST=127.0.0.1 +CHROMA_PORT=8000 +CHROMA_TENANT=default_tenant +CHROMA_SERVER_AUTHN_CREDENTIALS=difyai123456 +CHROMA_SERVER_AUTHN_PROVIDER=chromadb.auth.token_authn.TokenAuthenticationServerProvider +CHROMA_IS_PERSISTENT=TRUE diff --git a/480T/dify/envs/vectorstores/couchbase.env.example b/480T/dify/envs/vectorstores/couchbase.env.example new file mode 100755 index 0000000..4329d9c --- /dev/null +++ b/480T/dify/envs/vectorstores/couchbase.env.example @@ -0,0 +1,9 @@ +# ------------------------------ +# Couchbase Configuration +# ------------------------------ + +COUCHBASE_PASSWORD=password +COUCHBASE_BUCKET_NAME=Embeddings +COUCHBASE_SCOPE_NAME=_default +COUCHBASE_CONNECTION_STRING=couchbase://couchbase-server +COUCHBASE_USER=Administrator diff --git a/480T/dify/envs/vectorstores/elasticsearch.env.example b/480T/dify/envs/vectorstores/elasticsearch.env.example new file mode 100755 index 0000000..2aaa965 --- /dev/null +++ b/480T/dify/envs/vectorstores/elasticsearch.env.example @@ -0,0 +1,17 @@ +# ------------------------------ +# Elasticsearch Configuration +# ------------------------------ + +ELASTICSEARCH_CLOUD_URL=YOUR-ELASTICSEARCH_CLOUD_URL +ELASTICSEARCH_PASSWORD=elastic +KIBANA_PORT=5601 +ELASTICSEARCH_USE_CLOUD=false +ELASTICSEARCH_API_KEY=YOUR-ELASTICSEARCH_API_KEY +ELASTICSEARCH_VERIFY_CERTS=False +ELASTICSEARCH_CA_CERTS= +ELASTICSEARCH_REQUEST_TIMEOUT=100000 +ELASTICSEARCH_RETRY_ON_TIMEOUT=True +ELASTICSEARCH_MAX_RETRIES=10 +ELASTICSEARCH_HOST=0.0.0.0 +ELASTICSEARCH_PORT=9200 +ELASTICSEARCH_USERNAME=elastic diff --git a/480T/dify/envs/vectorstores/iris.env.example b/480T/dify/envs/vectorstores/iris.env.example new file mode 100755 index 0000000..b1eb39b --- /dev/null +++ b/480T/dify/envs/vectorstores/iris.env.example @@ -0,0 +1,17 @@ +# ------------------------------ +# Iris Configuration +# ------------------------------ + +IRIS_CONNECTION_URL= +IRIS_MIN_CONNECTION=1 +IRIS_MAX_CONNECTION=3 +IRIS_TEXT_INDEX=true +IRIS_TEXT_INDEX_LANGUAGE=en +IRIS_TIMEZONE=UTC +IRIS_PASSWORD=Dify@1234 +IRIS_DATABASE=USER +IRIS_SCHEMA=dify +IRIS_HOST=iris +IRIS_SUPER_SERVER_PORT=1972 +IRIS_WEB_SERVER_PORT=52773 +IRIS_USER=_SYSTEM diff --git a/480T/dify/envs/vectorstores/matrixone.env.example b/480T/dify/envs/vectorstores/matrixone.env.example new file mode 100755 index 0000000..931375f --- /dev/null +++ b/480T/dify/envs/vectorstores/matrixone.env.example @@ -0,0 +1,9 @@ +# ------------------------------ +# Matrixone Configuration +# ------------------------------ + +MATRIXONE_PASSWORD=111 +MATRIXONE_HOST=matrixone +MATRIXONE_PORT=6001 +MATRIXONE_USER=dump +MATRIXONE_DATABASE=dify diff --git a/480T/dify/envs/vectorstores/milvus.env.example b/480T/dify/envs/vectorstores/milvus.env.example new file mode 100755 index 0000000..c52feb7 --- /dev/null +++ b/480T/dify/envs/vectorstores/milvus.env.example @@ -0,0 +1,16 @@ +# ------------------------------ +# Milvus Configuration +# ------------------------------ + +MINIO_ACCESS_KEY=minioadmin +MINIO_SECRET_KEY=minioadmin +ETCD_ENDPOINTS=etcd:2379 +MINIO_ADDRESS=minio:9000 +ETCD_AUTO_COMPACTION_MODE=revision +ETCD_AUTO_COMPACTION_RETENTION=1000 +ETCD_QUOTA_BACKEND_BYTES=4294967296 +ETCD_SNAPSHOT_COUNT=50000 +MILVUS_AUTHORIZATION_ENABLED=true +MILVUS_SECURE=false +MILVUS_SERVER_PEM_PATH= +MILVUS_SERVER_NAME= diff --git a/480T/dify/envs/vectorstores/myscale.env.example b/480T/dify/envs/vectorstores/myscale.env.example new file mode 100755 index 0000000..eaa9e88 --- /dev/null +++ b/480T/dify/envs/vectorstores/myscale.env.example @@ -0,0 +1,10 @@ +# ------------------------------ +# Myscale Configuration +# ------------------------------ + +MYSCALE_PASSWORD= +MYSCALE_DATABASE=dify +MYSCALE_FTS_PARAMS= +MYSCALE_HOST=myscale +MYSCALE_PORT=8123 +MYSCALE_USER=default diff --git a/480T/dify/envs/vectorstores/oceanbase.env.example b/480T/dify/envs/vectorstores/oceanbase.env.example new file mode 100755 index 0000000..42bed8d --- /dev/null +++ b/480T/dify/envs/vectorstores/oceanbase.env.example @@ -0,0 +1,6 @@ +# ------------------------------ +# Oceanbase Configuration +# ------------------------------ + +OCEANBASE_CLUSTER_NAME=difyai +OCEANBASE_MEMORY_LIMIT=6G diff --git a/480T/dify/envs/vectorstores/opengauss.env.example b/480T/dify/envs/vectorstores/opengauss.env.example new file mode 100755 index 0000000..9f58499 --- /dev/null +++ b/480T/dify/envs/vectorstores/opengauss.env.example @@ -0,0 +1,12 @@ +# ------------------------------ +# Opengauss Configuration +# ------------------------------ + +OPENGAUSS_PASSWORD=Dify@123 +OPENGAUSS_DATABASE=dify +OPENGAUSS_MIN_CONNECTION=1 +OPENGAUSS_MAX_CONNECTION=5 +OPENGAUSS_ENABLE_PQ=false +OPENGAUSS_HOST=opengauss +OPENGAUSS_PORT=6600 +OPENGAUSS_USER=postgres diff --git a/480T/dify/envs/vectorstores/opensearch.env.example b/480T/dify/envs/vectorstores/opensearch.env.example new file mode 100755 index 0000000..a6a9283 --- /dev/null +++ b/480T/dify/envs/vectorstores/opensearch.env.example @@ -0,0 +1,22 @@ +# ------------------------------ +# Opensearch Configuration +# ------------------------------ + +OPENSEARCH_PASSWORD=admin +OPENSEARCH_AWS_REGION=ap-southeast-1 +OPENSEARCH_AWS_SERVICE=aoss +OPENSEARCH_INITIAL_ADMIN_PASSWORD=Qazwsxedc!@#123 +OPENSEARCH_MEMLOCK_SOFT=-1 +OPENSEARCH_MEMLOCK_HARD=-1 +OPENSEARCH_NOFILE_SOFT=65536 +OPENSEARCH_NOFILE_HARD=65536 +OPENSEARCH_HOST=opensearch +OPENSEARCH_PORT=9200 +OPENSEARCH_SECURE=true +OPENSEARCH_VERIFY_CERTS=true +OPENSEARCH_AUTH_METHOD=basic +OPENSEARCH_USER=admin +OPENSEARCH_DISCOVERY_TYPE=single-node +OPENSEARCH_BOOTSTRAP_MEMORY_LOCK=true +OPENSEARCH_JAVA_OPTS_MIN=512m +OPENSEARCH_JAVA_OPTS_MAX=1024m diff --git a/480T/dify/envs/vectorstores/oracle.env.example b/480T/dify/envs/vectorstores/oracle.env.example new file mode 100755 index 0000000..c8f24db --- /dev/null +++ b/480T/dify/envs/vectorstores/oracle.env.example @@ -0,0 +1,13 @@ +# ------------------------------ +# Oracle Configuration +# ------------------------------ + +ORACLE_PASSWORD=dify +ORACLE_DSN=oracle:1521/FREEPDB1 +ORACLE_CONFIG_DIR=/app/api/storage/wallet +ORACLE_WALLET_LOCATION=/app/api/storage/wallet +ORACLE_WALLET_PASSWORD=dify +ORACLE_IS_AUTONOMOUS=false +ORACLE_USER=dify +ORACLE_PWD=Dify123456 +ORACLE_CHARACTERSET=AL32UTF8 diff --git a/480T/dify/envs/vectorstores/pgvecto-rs.env.example b/480T/dify/envs/vectorstores/pgvecto-rs.env.example new file mode 100755 index 0000000..6428e5d --- /dev/null +++ b/480T/dify/envs/vectorstores/pgvecto-rs.env.example @@ -0,0 +1,9 @@ +# ------------------------------ +# Pgvecto Rs Configuration +# ------------------------------ + +PGVECTO_RS_HOST=pgvecto-rs +PGVECTO_RS_PORT=5432 +PGVECTO_RS_USER=postgres +PGVECTO_RS_PASSWORD=difyai123456 +PGVECTO_RS_DATABASE=dify diff --git a/480T/dify/envs/vectorstores/pgvector.env.example b/480T/dify/envs/vectorstores/pgvector.env.example new file mode 100755 index 0000000..9fd1dbf --- /dev/null +++ b/480T/dify/envs/vectorstores/pgvector.env.example @@ -0,0 +1,8 @@ +# ------------------------------ +# Pgvector Configuration +# ------------------------------ + +PGVECTOR_PGUSER=postgres +PGVECTOR_POSTGRES_PASSWORD=difyai123456 +PGVECTOR_POSTGRES_DB=dify +PGVECTOR_PGDATA=/var/lib/postgresql/data/pgdata diff --git a/480T/dify/envs/vectorstores/qdrant.env.example b/480T/dify/envs/vectorstores/qdrant.env.example new file mode 100755 index 0000000..a3555fe --- /dev/null +++ b/480T/dify/envs/vectorstores/qdrant.env.example @@ -0,0 +1,4 @@ +# ------------------------------ +# Qdrant Configuration +# ------------------------------ + diff --git a/480T/dify/envs/vectorstores/seekdb.env.example b/480T/dify/envs/vectorstores/seekdb.env.example new file mode 100755 index 0000000..4307fbe --- /dev/null +++ b/480T/dify/envs/vectorstores/seekdb.env.example @@ -0,0 +1,4 @@ +# ------------------------------ +# Seekdb Configuration +# ------------------------------ + diff --git a/480T/dify/envs/vectorstores/vastbase.env.example b/480T/dify/envs/vectorstores/vastbase.env.example new file mode 100755 index 0000000..2c9db50 --- /dev/null +++ b/480T/dify/envs/vectorstores/vastbase.env.example @@ -0,0 +1,11 @@ +# ------------------------------ +# Vastbase Configuration +# ------------------------------ + +VASTBASE_PASSWORD=Difyai123456 +VASTBASE_DATABASE=dify +VASTBASE_MIN_CONNECTION=1 +VASTBASE_MAX_CONNECTION=5 +VASTBASE_HOST=vastbase +VASTBASE_PORT=5432 +VASTBASE_USER=dify diff --git a/480T/dify/envs/vectorstores/weaviate.env.example b/480T/dify/envs/vectorstores/weaviate.env.example new file mode 100755 index 0000000..82a3ccb --- /dev/null +++ b/480T/dify/envs/vectorstores/weaviate.env.example @@ -0,0 +1,18 @@ +# ------------------------------ +# Weaviate Configuration +# ------------------------------ + +WEAVIATE_PERSISTENCE_DATA_PATH=/var/lib/weaviate +WEAVIATE_QUERY_DEFAULTS_LIMIT=25 +WEAVIATE_AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true +WEAVIATE_DEFAULT_VECTORIZER_MODULE=none +WEAVIATE_CLUSTER_HOSTNAME=node1 +WEAVIATE_AUTHENTICATION_APIKEY_ENABLED=true +WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih +WEAVIATE_AUTHENTICATION_APIKEY_USERS=hello@dify.ai +WEAVIATE_AUTHORIZATION_ADMINLIST_ENABLED=true +WEAVIATE_AUTHORIZATION_ADMINLIST_USERS=hello@dify.ai +WEAVIATE_DISABLE_TELEMETRY=false +WEAVIATE_ENABLE_TOKENIZER_GSE=false +WEAVIATE_ENABLE_TOKENIZER_KAGOME_JA=false +WEAVIATE_ENABLE_TOKENIZER_KAGOME_KR=false diff --git a/480T/dify/generate_docker_compose b/480T/dify/generate_docker_compose new file mode 100755 index 0000000..580091e --- /dev/null +++ b/480T/dify/generate_docker_compose @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +import os +import re +import sys + +# Variables that exist only for Docker Compose orchestration and must NOT be +# injected into containers as environment variables. +SHARED_ENV_EXCLUDE = frozenset( + [ + # Docker Compose profile selection + "COMPOSE_PROFILES", + # Worker health check orchestration flags (consumed by docker-compose, + # not by the application running inside the container) + "COMPOSE_WORKER_HEALTHCHECK_DISABLED", + "COMPOSE_WORKER_HEALTHCHECK_INTERVAL", + "COMPOSE_WORKER_HEALTHCHECK_TIMEOUT", + ] +) + + +def parse_env_example(file_path): + """ + Parses the .env.example file and returns a dictionary with variable names as keys and default values as values. + """ + env_vars = {} + with open(file_path, "r", encoding="utf-8") as f: + for line_number, line in enumerate(f, 1): + line = line.strip() + # Ignore empty lines and comments + if not line or line.startswith("#"): + continue + # Use regex to parse KEY=VALUE + match = re.match(r"^([^=]+)=(.*)$", line) + if match: + key = match.group(1).strip() + value = match.group(2).strip() + # Remove possible quotes around the value + if (value.startswith('"') and value.endswith('"')) or ( + value.startswith("'") and value.endswith("'") + ): + value = value[1:-1] + env_vars[key] = value + else: + print(f"Warning: Unable to parse line {line_number}: {line}") + return env_vars + + +def generate_shared_env_block(env_vars, anchor_name="shared-api-worker-env"): + """ + Generates a shared environment variables block as a YAML string. + """ + lines = [f"x-shared-env: &{anchor_name}"] + for key, default in env_vars.items(): + if key in SHARED_ENV_EXCLUDE: + continue + # If default value is empty, use ${KEY:-} + if default == "": + lines.append(f" {key}: ${{{key}:-}}") + else: + # If default value contains special characters, wrap it in quotes + if re.search(r"[:\s]", default): + default = f"{default}" + lines.append(f" {key}: ${{{key}:-{default}}}") + return "\n".join(lines) + + +def create_env_files_from_example(env_example_path): + """ + Creates actual env files from .env.example by copying the categorized .env.example files. + This allows docker-compose to use env_file references. + Supports per-module structure with subdirectories. + """ + base_dir = os.path.dirname(os.path.abspath(env_example_path)) + root_env_file = os.path.join(base_dir, ".env") + if not os.path.exists(root_env_file): + with open(env_example_path, "r", encoding="utf-8") as src, open( + root_env_file, "w", encoding="utf-8", newline="\n" + ) as dst: + dst.write(src.read()) + print(f"Created {root_env_file}") + else: + print(f"{root_env_file} already exists, skipping") + + envs_dir = os.path.join(base_dir, "envs") + if not os.path.isdir(envs_dir): + print(f"No envs directory found at {envs_dir}, skipping split env files") + return [] + + created_files = [] + # Walk through all .env.example files in subdirectories + for root, dirs, files in os.walk(envs_dir): + for file in files: + if file.endswith('.env.example'): + example_file = os.path.join(root, file) + env_file = example_file.replace('.env.example', '.env') + + if os.path.exists(env_file): + print(f"{env_file} already exists, skipping") + continue + + # Copy .example to actual file + with open(example_file, "r", encoding="utf-8") as src, open( + env_file, "w", encoding="utf-8", newline="\n" + ) as dst: + dst.write(src.read()) + created_files.append(env_file) + print(f"Created {env_file}") + + return created_files + + +def insert_shared_env(template_path, output_path, header_comments): + """ + Copies the template file to output path with header comments. + The template now uses env_file references instead of a huge YAML anchor. + """ + with open(template_path, "r", encoding="utf-8") as f: + template_content = f.read() + + # Prepare the final content with header comments + final_content = f"{header_comments}\n{template_content}" + + with open(output_path, "w", encoding="utf-8", newline="\n") as f: + f.write(final_content) + print(f"Generated {output_path}") + + +def main(): + base_dir = os.path.dirname(os.path.abspath(__file__)) + env_example_path = os.path.join(base_dir, ".env.example") + template_path = os.path.join(base_dir, "docker-compose-template.yaml") + output_path = os.path.join(base_dir, "docker-compose.yaml") + + # Define header comments to be added at the top of docker-compose.yaml + header_comments = ( + "# ==================================================================\n" + "# WARNING: This file is auto-generated by generate_docker_compose\n" + "# Do not modify this file directly. Instead, update the .env.example\n" + "# or docker-compose-template.yaml and regenerate this file.\n" + "# ==================================================================\n" + ) + + # Check if required files exist + for path in [env_example_path, template_path]: + if not os.path.isfile(path): + print(f"Error: File {path} does not exist.") + sys.exit(1) + + # Create env files from categorized .env.example files + # These files are used by docker-compose's env_file directive + # This ensures .env files exist even in CI/CD environments + create_env_files_from_example(env_example_path) + + # Copy template to output with header comments + # The template now uses env_file references instead of a huge YAML anchor + insert_shared_env(template_path, output_path, header_comments) + + +if __name__ == "__main__": + main() diff --git a/480T/dify/iris/docker-entrypoint.sh b/480T/dify/iris/docker-entrypoint.sh new file mode 100755 index 0000000..1a3b104 --- /dev/null +++ b/480T/dify/iris/docker-entrypoint.sh @@ -0,0 +1,56 @@ +#!/bin/bash +set -e + +# IRIS configuration flag file (stored in durable directory to persist with data) +IRIS_CONFIG_DONE="/durable/.iris-configured" + +# Function to wait for IRIS to be ready +wait_for_iris() { + echo "Waiting for IRIS to be ready..." + local max_attempts=30 + local attempt=1 + while [ "$attempt" -le "$max_attempts" ]; do + if iris qlist IRIS 2>/dev/null | grep -q "running"; then + echo "IRIS is ready." + return 0 + fi + echo "Attempt $attempt/$max_attempts: IRIS not ready yet, waiting..." + sleep 2 + attempt=$((attempt + 1)) + done + echo "ERROR: IRIS failed to start within expected time." >&2 + return 1 +} + +# Function to configure IRIS +configure_iris() { + echo "Configuring IRIS for first-time setup..." + + # Wait for IRIS to be fully started + wait_for_iris + + # Execute the initialization script + iris session IRIS < /iris-init.script + + # Mark configuration as done + touch "$IRIS_CONFIG_DONE" + + echo "IRIS configuration completed." +} + +# Start IRIS in background for initial configuration if not already configured +if [ ! -f "$IRIS_CONFIG_DONE" ]; then + echo "First-time IRIS setup detected. Starting IRIS for configuration..." + + # Start IRIS + iris start IRIS + + # Configure IRIS + configure_iris + + # Stop IRIS + iris stop IRIS quietly +fi + +# Run the original IRIS entrypoint +exec /iris-main "$@" diff --git a/480T/dify/iris/iris-init.script b/480T/dify/iris/iris-init.script new file mode 100755 index 0000000..c41fcf4 --- /dev/null +++ b/480T/dify/iris/iris-init.script @@ -0,0 +1,11 @@ +// Switch to the %SYS namespace to modify system settings +set $namespace="%SYS" + +// Set predefined user passwords to never expire (default password: SYS) +Do ##class(Security.Users).UnExpireUserPasswords("*") + +// Change the default password  +Do $SYSTEM.Security.ChangePassword("_SYSTEM","Dify@1234") + +// Install the Japanese locale (default is English since the container is Ubuntu-based) +// Do ##class(Config.NLS.Locales).Install("jpuw") diff --git a/480T/dify/nginx/conf.d/default.conf.template b/480T/dify/nginx/conf.d/default.conf.template new file mode 100755 index 0000000..6f17d0a --- /dev/null +++ b/480T/dify/nginx/conf.d/default.conf.template @@ -0,0 +1,73 @@ +# Please do not directly edit this file. Instead, modify the .env variables related to NGINX configuration. + +server { + listen ${NGINX_PORT}; + server_name ${NGINX_SERVER_NAME}; + + location /console/api { + proxy_pass http://api:5001; + include proxy.conf; + } + + location /api { + proxy_pass http://api:5001; + include proxy.conf; + } + + location /socket.io/ { + resolver 127.0.0.11 valid=30s ipv6=off; + set $socket_io_upstream ${NGINX_SOCKET_IO_UPSTREAM}; + proxy_pass http://$socket_io_upstream; + include proxy.conf; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_cache_bypass $http_upgrade; + } + + location /v1 { + proxy_pass http://api:5001; + include proxy.conf; + } + + location /openapi { + proxy_pass http://api:5001; + include proxy.conf; + } + + location /files { + proxy_pass http://api:5001; + include proxy.conf; + } + + location /explore { + proxy_pass http://web:3000; + include proxy.conf; + } + + location /e/ { + proxy_pass http://plugin_daemon:5002; + proxy_set_header Dify-Hook-Url $scheme://$host$request_uri; + include proxy.conf; + } + + location / { + proxy_pass http://web:3000; + include proxy.conf; + } + + location /mcp { + proxy_pass http://api:5001; + include proxy.conf; + } + + location /triggers { + proxy_pass http://api:5001; + include proxy.conf; + } + + # placeholder for acme challenge location + ${ACME_CHALLENGE_LOCATION} + + # placeholder for https config defined in https.conf.template + ${HTTPS_CONFIG} +} diff --git a/480T/dify/nginx/docker-entrypoint.sh b/480T/dify/nginx/docker-entrypoint.sh new file mode 100755 index 0000000..763254e --- /dev/null +++ b/480T/dify/nginx/docker-entrypoint.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +HTTPS_CONFIG='' + +if [ "${NGINX_HTTPS_ENABLED}" = "true" ]; then + # Check if the certificate and key files for the specified domain exist + if [ -n "${CERTBOT_DOMAIN}" ] && \ + [ -f "/etc/letsencrypt/live/${CERTBOT_DOMAIN}/${NGINX_SSL_CERT_FILENAME}" ] && \ + [ -f "/etc/letsencrypt/live/${CERTBOT_DOMAIN}/${NGINX_SSL_CERT_KEY_FILENAME}" ]; then + SSL_CERTIFICATE_PATH="/etc/letsencrypt/live/${CERTBOT_DOMAIN}/${NGINX_SSL_CERT_FILENAME}" + SSL_CERTIFICATE_KEY_PATH="/etc/letsencrypt/live/${CERTBOT_DOMAIN}/${NGINX_SSL_CERT_KEY_FILENAME}" + else + SSL_CERTIFICATE_PATH="/etc/ssl/${NGINX_SSL_CERT_FILENAME}" + SSL_CERTIFICATE_KEY_PATH="/etc/ssl/${NGINX_SSL_CERT_KEY_FILENAME}" + fi + export SSL_CERTIFICATE_PATH + export SSL_CERTIFICATE_KEY_PATH + + # set the HTTPS_CONFIG environment variable to the content of the https.conf.template + HTTPS_CONFIG=$(envsubst < /etc/nginx/https.conf.template) + export HTTPS_CONFIG + # Substitute the HTTPS_CONFIG in the default.conf.template with content from https.conf.template + envsubst '${HTTPS_CONFIG}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf +fi +export HTTPS_CONFIG + +if [ "${NGINX_ENABLE_CERTBOT_CHALLENGE}" = "true" ]; then + ACME_CHALLENGE_LOCATION='location /.well-known/acme-challenge/ { root /var/www/html; }' +else + ACME_CHALLENGE_LOCATION='' +fi +export ACME_CHALLENGE_LOCATION + +env_vars=$(printenv | cut -d= -f1 | sed 's/^/$/g' | paste -sd, -) + +envsubst "$env_vars" < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf +envsubst "$env_vars" < /etc/nginx/proxy.conf.template > /etc/nginx/proxy.conf + +envsubst "$env_vars" < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf + +# Start Nginx using the default entrypoint +exec nginx -g 'daemon off;' diff --git a/480T/dify/nginx/https.conf.template b/480T/dify/nginx/https.conf.template new file mode 100755 index 0000000..296908d --- /dev/null +++ b/480T/dify/nginx/https.conf.template @@ -0,0 +1,9 @@ +# Please do not directly edit this file. Instead, modify the .env variables related to NGINX configuration. + +listen ${NGINX_SSL_PORT} ssl; +ssl_certificate ${SSL_CERTIFICATE_PATH}; +ssl_certificate_key ${SSL_CERTIFICATE_KEY_PATH}; +ssl_protocols ${NGINX_SSL_PROTOCOLS}; +ssl_prefer_server_ciphers on; +ssl_session_cache shared:SSL:10m; +ssl_session_timeout 10m; diff --git a/480T/dify/nginx/nginx.conf.template b/480T/dify/nginx/nginx.conf.template new file mode 100755 index 0000000..20446fa --- /dev/null +++ b/480T/dify/nginx/nginx.conf.template @@ -0,0 +1,34 @@ +# Please do not directly edit this file. Instead, modify the .env variables related to NGINX configuration. + +user nginx; +worker_processes ${NGINX_WORKER_PROCESSES}; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout ${NGINX_KEEPALIVE_TIMEOUT}; + + #gzip on; + client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/480T/dify/nginx/proxy.conf.template b/480T/dify/nginx/proxy.conf.template new file mode 100755 index 0000000..117f806 --- /dev/null +++ b/480T/dify/nginx/proxy.conf.template @@ -0,0 +1,11 @@ +# Please do not directly edit this file. Instead, modify the .env variables related to NGINX configuration. + +proxy_set_header Host $host; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; +proxy_set_header X-Forwarded-Port $server_port; +proxy_http_version 1.1; +proxy_set_header Connection ""; +proxy_buffering off; +proxy_read_timeout ${NGINX_PROXY_READ_TIMEOUT}; +proxy_send_timeout ${NGINX_PROXY_SEND_TIMEOUT}; diff --git a/480T/dify/nginx/ssl/.gitkeep b/480T/dify/nginx/ssl/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/480T/dify/pgvector/docker-entrypoint.sh b/480T/dify/pgvector/docker-entrypoint.sh new file mode 100755 index 0000000..262eacf --- /dev/null +++ b/480T/dify/pgvector/docker-entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +PG_MAJOR=16 + +if [ "${PG_BIGM}" = "true" ]; then + # install pg_bigm + apt-get update + apt-get install -y curl make gcc postgresql-server-dev-${PG_MAJOR} + + curl -LO https://github.com/pgbigm/pg_bigm/archive/refs/tags/v${PG_BIGM_VERSION}.tar.gz + tar xf v${PG_BIGM_VERSION}.tar.gz + cd pg_bigm-${PG_BIGM_VERSION} || exit 1 + make USE_PGXS=1 PG_CONFIG=/usr/bin/pg_config + make USE_PGXS=1 PG_CONFIG=/usr/bin/pg_config install + + cd - || exit 1 + rm -rf v${PG_BIGM_VERSION}.tar.gz pg_bigm-${PG_BIGM_VERSION} + + # enable pg_bigm + sed -i -e 's/^#\s*shared_preload_libraries.*/shared_preload_libraries = '\''pg_bigm'\''/' /var/lib/postgresql/data/pgdata/postgresql.conf +fi + +# Run the original entrypoint script +exec /usr/local/bin/docker-entrypoint.sh postgres diff --git a/480T/dify/ssrf_proxy/docker-entrypoint.sh b/480T/dify/ssrf_proxy/docker-entrypoint.sh new file mode 100755 index 0000000..a19f981 --- /dev/null +++ b/480T/dify/ssrf_proxy/docker-entrypoint.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# Modified based on Squid OCI image entrypoint + +# This entrypoint aims to forward the squid logs to stdout to assist users of +# common container related tooling (e.g., kubernetes, docker-compose, etc) to +# access the service logs. + +# Moreover, it invokes the squid binary, leaving all the desired parameters to +# be provided by the "command" passed to the spawned container. If no command +# is provided by the user, the default behavior (as per the CMD statement in +# the Dockerfile) will be to use Ubuntu's default configuration [1] and run +# squid with the "-NYC" options to mimic the behavior of the Ubuntu provided +# systemd unit. + +# [1] The default configuration is changed in the Dockerfile to allow local +# network connections. See the Dockerfile for further information. + +echo "[ENTRYPOINT] re-create snakeoil self-signed certificate removed in the build process" +if [ ! -f /etc/ssl/private/ssl-cert-snakeoil.key ]; then + /usr/sbin/make-ssl-cert generate-default-snakeoil --force-overwrite > /dev/null 2>&1 +fi + +tail -F /var/log/squid/access.log 2>/dev/null & +tail -F /var/log/squid/error.log 2>/dev/null & +tail -F /var/log/squid/store.log 2>/dev/null & +tail -F /var/log/squid/cache.log 2>/dev/null & + +ALLOW_PRIVATE_CONF=/etc/squid/dify_allow_private.conf +SANDBOX_PROXY_CONF=/etc/squid/dify_sandbox_proxy.conf + +write_optional_private_allowlist() { + local env_name="$1" + local acl_name="$2" + local acl_type="$3" + local raw_values="${!env_name:-}" + + raw_values="${raw_values//,/ }" + + if [ -z "${raw_values//[[:space:]]/}" ]; then + return + fi + + printf 'acl %s %s' "$acl_name" "$acl_type" >> "$ALLOW_PRIVATE_CONF" + for value in $raw_values; do + printf ' %s' "$value" >> "$ALLOW_PRIVATE_CONF" + done + printf '\nhttp_access allow client_localnet %s\n' "$acl_name" >> "$ALLOW_PRIVATE_CONF" +} + +{ + echo "# Generated by docker-entrypoint.sh." + echo "# Allows selected private targets before the default private-network deny rule." +} > "$ALLOW_PRIVATE_CONF" +write_optional_private_allowlist "SSRF_PROXY_ALLOW_PRIVATE_IPS" "dify_allowed_private_networks" "dst" +write_optional_private_allowlist "SSRF_PROXY_ALLOW_PRIVATE_DOMAINS" "dify_allowed_private_domains" "dstdomain" + +{ + echo "# Generated by docker-entrypoint.sh." + echo "# Enables the middleware-only sandbox host bridge when configured." +} > "$SANDBOX_PROXY_CONF" + +if [ -n "${SSRF_SANDBOX_PROXY_PORT:-}" ]; then + sandbox_proxy_host="${SSRF_SANDBOX_PROXY_HOST:-sandbox}" + sandbox_proxy_target_port="${SANDBOX_PORT:-8194}" + + { + printf 'http_port %s accel vhost\n' "$SSRF_SANDBOX_PROXY_PORT" + printf 'cache_peer %s parent %s 0 no-query originserver name=dify_sandbox\n' \ + "$sandbox_proxy_host" \ + "$sandbox_proxy_target_port" + printf 'acl dify_sandbox_proxy_port localport %s\n' "$SSRF_SANDBOX_PROXY_PORT" + printf 'http_access allow dify_sandbox_proxy_port\n' + } >> "$SANDBOX_PROXY_CONF" +fi + +# Replace environment variables in the template and output to the squid.conf +echo "[ENTRYPOINT] replacing environment variables in the template" +awk '{ + while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) { + var = substr($0, RSTART+2, RLENGTH-3) + val = ENVIRON[var] + $0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH) + } + print +}' /etc/squid/squid.conf.template > /etc/squid/squid.conf + +/usr/sbin/squid -Nz +echo "[ENTRYPOINT] starting squid" +/usr/sbin/squid -f /etc/squid/squid.conf -NYC 1 diff --git a/480T/dify/ssrf_proxy/squid.conf.template b/480T/dify/ssrf_proxy/squid.conf.template new file mode 100755 index 0000000..6e30cdb --- /dev/null +++ b/480T/dify/ssrf_proxy/squid.conf.template @@ -0,0 +1,121 @@ +acl client_localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN) +acl client_localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN) +acl client_localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN) +acl client_localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines +acl client_localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN) +acl client_localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN) +acl client_localnet src fc00::/7 # RFC 4193 local private network range +acl client_localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines +acl to_private_networks dst 0.0.0.0/8 +acl to_private_networks dst 10.0.0.0/8 +acl to_private_networks dst 100.64.0.0/10 +acl to_private_networks dst 127.0.0.0/8 +acl to_private_networks dst 169.254.0.0/16 +acl to_private_networks dst 172.16.0.0/12 +acl to_private_networks dst 192.168.0.0/16 +acl to_private_networks dst 224.0.0.0/4 +acl to_private_networks dst 240.0.0.0/4 +acl to_private_networks dst ::/128 +acl to_private_networks dst ::1/128 +acl to_private_networks dst ::ffff:0:0/96 # IPv4-mapped +acl to_private_networks dst ::/96 # deprecated IPv4-compatible +acl to_private_networks dst fc00::/7 +acl to_private_networks dst fe80::/10 +acl SSL_ports port 443 +# acl SSL_ports port 1025-65535 # Enable the configuration to resolve this issue: https://github.com/langgenius/dify/issues/12792 +acl Safe_ports port 80 # http +acl Safe_ports port 21 # ftp +acl Safe_ports port 443 # https +acl Safe_ports port 70 # gopher +acl Safe_ports port 210 # wais +acl Safe_ports port 1025-65535 # unregistered ports +acl Safe_ports port 280 # http-mgmt +acl Safe_ports port 488 # gss-http +acl Safe_ports port 591 # filemaker +acl Safe_ports port 777 # multiling http +acl CONNECT method CONNECT +acl allowed_domains dstdomain .marketplace.dify.ai + +http_port ${HTTP_PORT} + +http_access deny !Safe_ports +http_access deny CONNECT !SSL_ports +http_access allow localhost manager +http_access deny manager +include /etc/squid/dify_sandbox_proxy.conf +include /etc/squid/dify_allow_private.conf +http_access deny to_private_networks +http_access allow allowed_domains +http_access allow client_localnet +http_access allow localhost +http_access deny all +tcp_outgoing_address 0.0.0.0 + +################################## Proxy Server ################################ +coredump_dir ${COREDUMP_DIR} +refresh_pattern ^ftp: 1440 20% 10080 +refresh_pattern ^gopher: 1440 0% 1440 +refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 +refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims +refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims +refresh_pattern \/InRelease$ 0 0% 0 refresh-ims +refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims +refresh_pattern . 0 20% 4320 + + +# cache_dir ufs /var/spool/squid 100 16 256 +# upstream proxy, set to your own upstream proxy IP to avoid SSRF attacks +# cache_peer 172.1.1.1 parent 3128 0 no-query no-digest no-netdb-exchange default + +################################## Request Buffer ################################ + +# Unless the option's size is increased, an error will occur when uploading more than two files. +client_request_buffer_max_size 100 MB + +################################## Performance & Concurrency ############################### +# Increase file descriptor limit for high concurrency +max_filedescriptors 65536 + +# Timeout configurations for image requests +connect_timeout 30 seconds +request_timeout 2 minutes +read_timeout 2 minutes +client_lifetime 5 minutes +shutdown_lifetime 30 seconds + +# Persistent connections - improve performance for multiple requests +server_persistent_connections on +client_persistent_connections on +persistent_request_timeout 30 seconds +pconn_timeout 1 minute + +# Connection pool and concurrency limits +client_db on +server_idle_pconn_timeout 2 minutes +client_idle_pconn_timeout 2 minutes + +# Quick abort settings - don't abort requests that are mostly done +quick_abort_min 16 KB +quick_abort_max 16 MB +quick_abort_pct 95 + +# Memory and cache optimization +memory_cache_mode disk +cache_mem 256 MB +maximum_object_size_in_memory 512 KB + +# DNS resolver settings for better performance +dns_timeout 30 seconds +dns_retransmit_interval 5 seconds +# By default, Squid uses the system's configured DNS resolvers. +# If you need to override them, set dns_nameservers to appropriate servers +# for your environment (for example, internal/corporate DNS). The following +# is an example using public DNS and SHOULD be customized before use: +# dns_nameservers 8.8.8.8 8.8.4.4 + +# Logging format for better debugging +logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %/dev/null 2>&1 || true + docker rm -f "$SANDBOX_CONTAINER_NAME" >/dev/null 2>&1 || true + docker network rm "$NETWORK_NAME" >/dev/null 2>&1 || true +} + +http_code_for() { + local proxy_url="$1" + local target_url="$2" + local output + + output="$( + docker run \ + --rm \ + --network "$NETWORK_NAME" \ + --env "http_proxy=$proxy_url" \ + --env "https_proxy=$proxy_url" \ + "$CLIENT_IMAGE" \ + wget -S -O /dev/null -T 10 "$target_url" 2>&1 || true + )" + + printf '%s\n' "$output" | awk '$1 ~ /^HTTP\// { code = $2 } END { print code }' +} + +direct_http_code_for() { + local target_url="$1" + local output + + output="$( + docker run \ + --rm \ + --network "$NETWORK_NAME" \ + "$CLIENT_IMAGE" \ + wget -S -O /dev/null -T 10 "$target_url" 2>&1 || true + )" + + printf '%s\n' "$output" | awk '$1 ~ /^HTTP\// { code = $2 } END { print code }' +} + +assert_private_target_blocked() { + local proxy_url="$1" + local target_url="$2" + local status_code + + status_code="$(http_code_for "$proxy_url" "$target_url")" + if [[ "$status_code" != "403" ]]; then + echo "Expected $target_url to be blocked with HTTP 403, got ${status_code:-no response}." + docker logs "$CONTAINER_NAME" >&2 || true + exit 1 + fi +} + +assert_public_target_allowed() { + local proxy_url="$1" + local target_url="$2" + local status_code + + status_code="$(http_code_for "$proxy_url" "$target_url")" + if [[ ! "$status_code" =~ ^[234][0-9][0-9]$ || "$status_code" == "403" ]]; then + echo "Expected $target_url to remain reachable, got ${status_code:-no response}." + docker logs "$CONTAINER_NAME" >&2 || true + exit 1 + fi +} + +assert_sandbox_bridge_allowed() { + local target_url="$1" + local status_code + + status_code="$(direct_http_code_for "$target_url")" + if [[ ! "$status_code" =~ ^2[0-9][0-9]$ ]]; then + echo "Expected sandbox host bridge $target_url to remain reachable, got ${status_code:-no response}." + docker logs "$CONTAINER_NAME" >&2 || true + docker logs "$SANDBOX_CONTAINER_NAME" >&2 || true + exit 1 + fi +} + +trap cleanup EXIT +cleanup +docker network create "$NETWORK_NAME" >/dev/null + +docker run \ + --detach \ + --name "$SANDBOX_CONTAINER_NAME" \ + --network "$NETWORK_NAME" \ + --network-alias sandbox \ + "$CLIENT_IMAGE" \ + sh -c "mkdir -p /www && echo ok > /www/health && httpd -f -p 8194 -h /www" \ + >/dev/null + +docker run \ + --detach \ + --name "$CONTAINER_NAME" \ + --entrypoint sh \ + --network "$NETWORK_NAME" \ + --volume "$ROOT_DIR/docker/ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template:ro" \ + --volume "$ROOT_DIR/docker/ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh:ro" \ + --env HTTP_PORT=3128 \ + --env COREDUMP_DIR=/var/spool/squid \ + --env SSRF_SANDBOX_PROXY_PORT=8194 \ + --env SSRF_SANDBOX_PROXY_HOST=sandbox \ + --env "SSRF_PROXY_ALLOW_PRIVATE_IPS=${SSRF_PROXY_ALLOW_PRIVATE_IPS:-}" \ + --env "SSRF_PROXY_ALLOW_PRIVATE_DOMAINS=${SSRF_PROXY_ALLOW_PRIVATE_DOMAINS:-}" \ + "$IMAGE" \ + -c "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh" \ + >/dev/null + +proxy_url="http://$CONTAINER_NAME:3128" +for _ in {1..30}; do + probe_status="$(http_code_for "$proxy_url" "http://127.0.0.1:80/")" + if [[ -n "$probe_status" ]]; then + break + fi + sleep 1 +done + +if [[ -z "${probe_status:-}" ]]; then + echo "Squid proxy did not respond to probes." + docker logs "$CONTAINER_NAME" >&2 || true + exit 1 +fi + +assert_private_target_blocked "$proxy_url" "http://127.0.0.1:80/" +assert_private_target_blocked "$proxy_url" "http://0.1.2.3:80/" +assert_private_target_blocked "$proxy_url" "http://169.254.169.254/latest/meta-data/" + +if [[ "$RUN_PUBLIC_CHECK" == "true" ]]; then + assert_public_target_allowed "$proxy_url" "http://example.com/" +fi + +assert_sandbox_bridge_allowed "http://$CONTAINER_NAME:8194/health" diff --git a/480T/dify/startupscripts/init.sh b/480T/dify/startupscripts/init.sh new file mode 100755 index 0000000..dcee1e1 --- /dev/null +++ b/480T/dify/startupscripts/init.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +DB_INITIALIZED="/opt/oracle/oradata/dbinit" +#[ -f ${DB_INITIALIZED} ] && exit +#touch ${DB_INITIALIZED} +if [ -f ${DB_INITIALIZED} ]; then + echo 'File exists. Standards for have been Init' + exit +else + echo 'File does not exist. Standards for first time Start up this DB' + "$ORACLE_HOME"/bin/sqlplus -s "/ as sysdba" @"/opt/oracle/scripts/startup/init_user.script"; + touch ${DB_INITIALIZED} +fi diff --git a/480T/dify/startupscripts/init_user.script b/480T/dify/startupscripts/init_user.script new file mode 100755 index 0000000..e710d82 --- /dev/null +++ b/480T/dify/startupscripts/init_user.script @@ -0,0 +1,10 @@ +show pdbs; +ALTER SYSTEM SET PROCESSES=500 SCOPE=SPFILE; +alter session set container= freepdb1; +create user dify identified by dify DEFAULT TABLESPACE users quota unlimited on users; +grant DB_DEVELOPER_ROLE to dify; + +BEGIN +CTX_DDL.CREATE_PREFERENCE('dify.world_lexer','WORLD_LEXER'); +END; +/ diff --git a/480T/dify/tidb/config/pd.toml b/480T/dify/tidb/config/pd.toml new file mode 100755 index 0000000..01e352a --- /dev/null +++ b/480T/dify/tidb/config/pd.toml @@ -0,0 +1,4 @@ +# PD Configuration File reference: +# https://docs.pingcap.com/tidb/stable/pd-configuration-file#pd-configuration-file +[replication] +max-replicas = 1 diff --git a/480T/dify/tidb/config/tiflash-learner.toml b/480T/dify/tidb/config/tiflash-learner.toml new file mode 100755 index 0000000..5098829 --- /dev/null +++ b/480T/dify/tidb/config/tiflash-learner.toml @@ -0,0 +1,13 @@ +# TiFlash tiflash-learner.toml Configuration File reference: +# https://docs.pingcap.com/tidb/stable/tiflash-configuration#configure-the-tiflash-learnertoml-file + +log-file = "/logs/tiflash_tikv.log" + +[server] +engine-addr = "tiflash:4030" +addr = "0.0.0.0:20280" +advertise-addr = "tiflash:20280" +status-addr = "tiflash:20292" + +[storage] +data-dir = "/data/flash" diff --git a/480T/dify/tidb/config/tiflash.toml b/480T/dify/tidb/config/tiflash.toml new file mode 100755 index 0000000..30ac13e --- /dev/null +++ b/480T/dify/tidb/config/tiflash.toml @@ -0,0 +1,19 @@ +# TiFlash tiflash.toml Configuration File reference: +# https://docs.pingcap.com/tidb/stable/tiflash-configuration#configure-the-tiflashtoml-file + +listen_host = "0.0.0.0" +path = "/data" + +[flash] +tidb_status_addr = "tidb:10080" +service_addr = "tiflash:4030" + +[flash.proxy] +config = "/tiflash-learner.toml" + +[logger] +errorlog = "/logs/tiflash_error.log" +log = "/logs/tiflash.log" + +[raft] +pd_addr = "pd0:2379" diff --git a/480T/dify/tidb/docker-compose.yaml b/480T/dify/tidb/docker-compose.yaml new file mode 100755 index 0000000..9db6922 --- /dev/null +++ b/480T/dify/tidb/docker-compose.yaml @@ -0,0 +1,63 @@ +services: + pd0: + image: pingcap/pd:v8.5.1 + # ports: + # - "2379" + volumes: + - ./config/pd.toml:/pd.toml:ro + - ./volumes/data:/data + - ./volumes/logs:/logs + command: + - --name=pd0 + - --client-urls=http://0.0.0.0:2379 + - --peer-urls=http://0.0.0.0:2380 + - --advertise-client-urls=http://pd0:2379 + - --advertise-peer-urls=http://pd0:2380 + - --initial-cluster=pd0=http://pd0:2380 + - --data-dir=/data/pd + - --config=/pd.toml + - --log-file=/logs/pd.log + restart: on-failure + tikv: + image: pingcap/tikv:v8.5.1 + volumes: + - ./volumes/data:/data + - ./volumes/logs:/logs + command: + - --addr=0.0.0.0:20160 + - --advertise-addr=tikv:20160 + - --status-addr=tikv:20180 + - --data-dir=/data/tikv + - --pd=pd0:2379 + - --log-file=/logs/tikv.log + depends_on: + - "pd0" + restart: on-failure + tidb: + image: pingcap/tidb:v8.5.1 + # ports: + # - "4000:4000" + volumes: + - ./volumes/logs:/logs + command: + - --advertise-address=tidb + - --store=tikv + - --path=pd0:2379 + - --log-file=/logs/tidb.log + depends_on: + - "tikv" + restart: on-failure + tiflash: + image: pingcap/tiflash:v8.5.1 + volumes: + - ./config/tiflash.toml:/tiflash.toml:ro + - ./config/tiflash-learner.toml:/tiflash-learner.toml:ro + - ./volumes/data:/data + - ./volumes/logs:/logs + command: + - server + - --config-file=/tiflash.toml + depends_on: + - "tikv" + - "tidb" + restart: on-failure diff --git a/480T/dify/volumes/myscale/config/users.d/custom_users_config.xml b/480T/dify/volumes/myscale/config/users.d/custom_users_config.xml new file mode 100755 index 0000000..b46e73a --- /dev/null +++ b/480T/dify/volumes/myscale/config/users.d/custom_users_config.xml @@ -0,0 +1,17 @@ + + + + + + ::1 + 127.0.0.1 + 10.0.0.0/8 + 172.16.0.0/12 + 192.168.0.0/16 + + default + default + 1 + + + diff --git a/480T/dify/volumes/oceanbase/init.d/vec_memory.sql b/480T/dify/volumes/oceanbase/init.d/vec_memory.sql new file mode 100755 index 0000000..0d859e5 --- /dev/null +++ b/480T/dify/volumes/oceanbase/init.d/vec_memory.sql @@ -0,0 +1 @@ +ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30; diff --git a/480T/dify/volumes/opensearch/opensearch_dashboards.yml b/480T/dify/volumes/opensearch/opensearch_dashboards.yml new file mode 100755 index 0000000..f50d63b --- /dev/null +++ b/480T/dify/volumes/opensearch/opensearch_dashboards.yml @@ -0,0 +1,222 @@ +--- +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 + +# Description: +# Default configuration for OpenSearch Dashboards + +# OpenSearch Dashboards is served by a back end server. This setting specifies the port to use. +# server.port: 5601 + +# Specifies the address to which the OpenSearch Dashboards server will bind. IP addresses and host names are both valid values. +# The default is 'localhost', which usually means remote machines will not be able to connect. +# To allow connections from remote users, set this parameter to a non-loopback address. +# server.host: "localhost" + +# Enables you to specify a path to mount OpenSearch Dashboards at if you are running behind a proxy. +# Use the `server.rewriteBasePath` setting to tell OpenSearch Dashboards if it should remove the basePath +# from requests it receives, and to prevent a deprecation warning at startup. +# This setting cannot end in a slash. +# server.basePath: "" + +# Specifies whether OpenSearch Dashboards should rewrite requests that are prefixed with +# `server.basePath` or require that they are rewritten by your reverse proxy. +# server.rewriteBasePath: false + +# The maximum payload size in bytes for incoming server requests. +# server.maxPayloadBytes: 1048576 + +# The OpenSearch Dashboards server's name. This is used for display purposes. +# server.name: "your-hostname" + +# The URLs of the OpenSearch instances to use for all your queries. +# opensearch.hosts: ["http://localhost:9200"] + +# OpenSearch Dashboards uses an index in OpenSearch to store saved searches, visualizations and +# dashboards. OpenSearch Dashboards creates a new index if the index doesn't already exist. +# opensearchDashboards.index: ".opensearch_dashboards" + +# The default application to load. +# opensearchDashboards.defaultAppId: "home" + +# Setting for an optimized healthcheck that only uses the local OpenSearch node to do Dashboards healthcheck. +# This settings should be used for large clusters or for clusters with ingest heavy nodes. +# It allows Dashboards to only healthcheck using the local OpenSearch node rather than fan out requests across all nodes. +# +# It requires the user to create an OpenSearch node attribute with the same name as the value used in the setting +# This node attribute should assign all nodes of the same cluster an integer value that increments with each new cluster that is spun up +# e.g. in opensearch.yml file you would set the value to a setting using node.attr.cluster_id: +# Should only be enabled if there is a corresponding node attribute created in your OpenSearch config that matches the value here +# opensearch.optimizedHealthcheckId: "cluster_id" + +# If your OpenSearch is protected with basic authentication, these settings provide +# the username and password that the OpenSearch Dashboards server uses to perform maintenance on the OpenSearch Dashboards +# index at startup. Your OpenSearch Dashboards users still need to authenticate with OpenSearch, which +# is proxied through the OpenSearch Dashboards server. +# opensearch.username: "opensearch_dashboards_system" +# opensearch.password: "pass" + +# Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively. +# These settings enable SSL for outgoing requests from the OpenSearch Dashboards server to the browser. +# server.ssl.enabled: false +# server.ssl.certificate: /path/to/your/server.crt +# server.ssl.key: /path/to/your/server.key + +# Optional settings that provide the paths to the PEM-format SSL certificate and key files. +# These files are used to verify the identity of OpenSearch Dashboards to OpenSearch and are required when +# xpack.security.http.ssl.client_authentication in OpenSearch is set to required. +# opensearch.ssl.certificate: /path/to/your/client.crt +# opensearch.ssl.key: /path/to/your/client.key + +# Optional setting that enables you to specify a path to the PEM file for the certificate +# authority for your OpenSearch instance. +# opensearch.ssl.certificateAuthorities: [ "/path/to/your/CA.pem" ] + +# To disregard the validity of SSL certificates, change this setting's value to 'none'. +# opensearch.ssl.verificationMode: full + +# Time in milliseconds to wait for OpenSearch to respond to pings. Defaults to the value of +# the opensearch.requestTimeout setting. +# opensearch.pingTimeout: 1500 + +# Time in milliseconds to wait for responses from the back end or OpenSearch. This value +# must be a positive integer. +# opensearch.requestTimeout: 30000 + +# List of OpenSearch Dashboards client-side headers to send to OpenSearch. To send *no* client-side +# headers, set this value to [] (an empty list). +# opensearch.requestHeadersWhitelist: [ authorization ] + +# Header names and values that are sent to OpenSearch. Any custom headers cannot be overwritten +# by client-side headers, regardless of the opensearch.requestHeadersWhitelist configuration. +# opensearch.customHeaders: {} + +# Time in milliseconds for OpenSearch to wait for responses from shards. Set to 0 to disable. +# opensearch.shardTimeout: 30000 + +# Logs queries sent to OpenSearch. Requires logging.verbose set to true. +# opensearch.logQueries: false + +# Specifies the path where OpenSearch Dashboards creates the process ID file. +# pid.file: /var/run/opensearchDashboards.pid + +# Enables you to specify a file where OpenSearch Dashboards stores log output. +# logging.dest: stdout + +# Set the value of this setting to true to suppress all logging output. +# logging.silent: false + +# Set the value of this setting to true to suppress all logging output other than error messages. +# logging.quiet: false + +# Set the value of this setting to true to log all events, including system usage information +# and all requests. +# logging.verbose: false + +# Set the interval in milliseconds to sample system and process performance +# metrics. Minimum is 100ms. Defaults to 5000. +# ops.interval: 5000 + +# Specifies locale to be used for all localizable strings, dates and number formats. +# Supported languages are the following: English - en , by default , Chinese - zh-CN . +# i18n.locale: "en" + +# Set the allowlist to check input graphite Url. Allowlist is the default check list. +# vis_type_timeline.graphiteAllowedUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'] + +# Set the blocklist to check input graphite Url. Blocklist is an IP list. +# Below is an example for reference +# vis_type_timeline.graphiteBlockedIPs: [ +# //Loopback +# '127.0.0.0/8', +# '::1/128', +# //Link-local Address for IPv6 +# 'fe80::/10', +# //Private IP address for IPv4 +# '10.0.0.0/8', +# '172.16.0.0/12', +# '192.168.0.0/16', +# //Unique local address (ULA) +# 'fc00::/7', +# //Reserved IP address +# '0.0.0.0/8', +# '100.64.0.0/10', +# '192.0.0.0/24', +# '192.0.2.0/24', +# '198.18.0.0/15', +# '192.88.99.0/24', +# '198.51.100.0/24', +# '203.0.113.0/24', +# '224.0.0.0/4', +# '240.0.0.0/4', +# '255.255.255.255/32', +# '::/128', +# '2001:db8::/32', +# 'ff00::/8', +# ] +# vis_type_timeline.graphiteBlockedIPs: [] + +# opensearchDashboards.branding: +# logo: +# defaultUrl: "" +# darkModeUrl: "" +# mark: +# defaultUrl: "" +# darkModeUrl: "" +# loadingLogo: +# defaultUrl: "" +# darkModeUrl: "" +# faviconUrl: "" +# applicationTitle: "" + +# Set the value of this setting to true to capture region blocked warnings and errors +# for your map rendering services. +# map.showRegionBlockedWarning: false% + +# Set the value of this setting to false to suppress search usage telemetry +# for reducing the load of OpenSearch cluster. +# data.search.usageTelemetry.enabled: false + +# 2.4 renames 'wizard.enabled: false' to 'vis_builder.enabled: false' +# Set the value of this setting to false to disable VisBuilder +# functionality in Visualization. +# vis_builder.enabled: false + +# 2.4 New Experimental Feature +# Set the value of this setting to true to enable the experimental multiple data source +# support feature. Use with caution. +# data_source.enabled: false +# Set the value of these settings to customize crypto materials to encryption saved credentials +# in data sources. +# data_source.encryption.wrappingKeyName: 'changeme' +# data_source.encryption.wrappingKeyNamespace: 'changeme' +# data_source.encryption.wrappingKey: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + +# 2.6 New ML Commons Dashboards Feature +# Set the value of this setting to true to enable the ml commons dashboards +# ml_commons_dashboards.enabled: false + +# 2.12 New experimental Assistant Dashboards Feature +# Set the value of this setting to true to enable the assistant dashboards +# assistant.chat.enabled: false + +# 2.13 New Query Assistant Feature +# Set the value of this setting to false to disable the query assistant +# observability.query_assist.enabled: false + +# 2.14 Enable Ui Metric Collectors in Usage Collector +# Set the value of this setting to true to enable UI Metric collections +# usageCollection.uiMetric.enabled: false + +opensearch.hosts: [https://localhost:9200] +opensearch.ssl.verificationMode: none +opensearch.username: admin +opensearch.password: 'Qazwsxedc!@#123' +opensearch.requestHeadersWhitelist: [authorization, securitytenant] + +opensearch_security.multitenancy.enabled: true +opensearch_security.multitenancy.tenants.preferred: [Private, Global] +opensearch_security.readonly_mode.roles: [kibana_read_only] +# Use this setting if you are running opensearch-dashboards without https +opensearch_security.cookie.secure: false +server.host: '0.0.0.0' diff --git a/480T/dify/volumes/sandbox/conf/config.yaml b/480T/dify/volumes/sandbox/conf/config.yaml new file mode 100755 index 0000000..31b5a59 --- /dev/null +++ b/480T/dify/volumes/sandbox/conf/config.yaml @@ -0,0 +1,13 @@ +app: + port: 8194 + debug: True + key: dify-sandbox +max_workers: 4 +max_requests: 50 +worker_timeout: 5 +enable_network: True # please make sure there is no network risk in your environment +allowed_syscalls: # please leave it empty if you have no idea how seccomp works +proxy: + socks5: '' + http: '' + https: '' diff --git a/480T/dify/volumes/sandbox/conf/config.yaml.example b/480T/dify/volumes/sandbox/conf/config.yaml.example new file mode 100755 index 0000000..365089c --- /dev/null +++ b/480T/dify/volumes/sandbox/conf/config.yaml.example @@ -0,0 +1,35 @@ +app: + port: 8194 + debug: True + key: dify-sandbox +max_workers: 4 +max_requests: 50 +worker_timeout: 5 +python_path: /opt/python/bin/python3 +python_lib_path: + - /usr/local/lib/python3.10 + - /usr/lib/python3.10 + - /usr/lib/python3 + - /usr/lib/x86_64-linux-gnu + - /etc/ssl/certs/ca-certificates.crt + - /etc/nsswitch.conf + - /etc/hosts + - /etc/resolv.conf + - /run/systemd/resolve/stub-resolv.conf + - /run/resolvconf/resolv.conf + - /etc/localtime + - /usr/share/zoneinfo + - /etc/timezone + # add more paths if needed +python_pip_mirror_url: https://pypi.tuna.tsinghua.edu.cn/simple +nodejs_path: /usr/local/bin/node +enable_network: True +allowed_syscalls: + - 1 + - 2 + - 3 + # add all the syscalls which you require +proxy: + socks5: '' + http: '' + https: ''