From f5cbb063a1c98bd43d5d783772f39adf129756b6 Mon Sep 17 00:00:00 2001 From: maximilion96 Date: Wed, 24 Jun 2026 01:14:17 +0300 Subject: [PATCH] Add Dify stack to ru-docker-infra MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 7 сервисов (api, worker, web, plugin-daemon, sandbox, ssrf-proxy, weaviate) на общих postgres (базы dify/dify_plugin) и redis (db 2/3). Vector store — weaviate. Внешний вход через traefik path-роутингом на dify.mxti.ru. Добавлены: ssrf_proxy/squid.conf, postgres init для баз, DNS-запись, сервис dify в inventory, deploy-ключ в ssh/. Co-Authored-By: Claude Opus 4.8 --- domains.yml | 5 + servers/inventory.yml | 3 +- servers/ru-docker-infra/.env | 15 ++ .../dify/ssrf_proxy/squid.conf | 52 +++++ servers/ru-docker-infra/docker-compose.yml | 219 ++++++++++++++++++ .../postgres/init/10-dify-databases.sql | 10 + servers/ru-docker-infra/ssh/deploy_key | 25 ++ 7 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 servers/ru-docker-infra/dify/ssrf_proxy/squid.conf create mode 100644 servers/ru-docker-infra/postgres/init/10-dify-databases.sql create mode 100644 servers/ru-docker-infra/ssh/deploy_key diff --git a/domains.yml b/domains.yml index 67cd857..8f5fb68 100644 --- a/domains.yml +++ b/domains.yml @@ -26,6 +26,11 @@ domains: server: ru-docker-infra purpose: "portainer UI" + - host: dify + type: A + server: ru-docker-infra + purpose: "dify через traefik" + - host: n8n type: A server: nl-reverse-proxy diff --git a/servers/inventory.yml b/servers/inventory.yml index f9b2dce..e0f3938 100644 --- a/servers/inventory.yml +++ b/servers/inventory.yml @@ -12,7 +12,7 @@ servers: role: core # основной узел: все ключевые сервисы env: prod location: yandex-cloud - tags: [traefik, n8n, gitea, postgres, redis, portainer, xray, mtg] + tags: [traefik, n8n, gitea, postgres, redis, portainer, xray, mtg, dify] services: - traefik - postgres @@ -23,6 +23,7 @@ servers: - gitea - portainer - mtg # Telegram MTProto-прокси + - dify # LLM-app платформа (api/worker/web/plugin-daemon/sandbox/ssrf-proxy/weaviate) managed: true - name: ru-vpn # входная нода VPN diff --git a/servers/ru-docker-infra/.env b/servers/ru-docker-infra/.env index ccdce78..2daeb09 100644 --- a/servers/ru-docker-infra/.env +++ b/servers/ru-docker-infra/.env @@ -22,6 +22,21 @@ REDIS_PASSWORD=31hjsbj34rjs # Сгенерируй: openssl rand -hex 32 N8N_ENCRYPTION_KEY=fbac490fbf63b779fb5f3d90a2fe5445164aaacef1506b02f3f4331c23c2ba59 +# --- Dify --- +DIFY_HOST=dify.mxti.ru +# Версии образов (api и web всегда одинаковые) +DIFY_VERSION=1.4.3 +DIFY_PLUGIN_DAEMON_VERSION=0.1.2-local +DIFY_SANDBOX_VERSION=0.2.12 +# Секреты (сгенерированы: openssl rand -base64 42 / -hex 24) +DIFY_SECRET_KEY=UzWUvFZzVB0Ap0MhhEfQ4r4YiRUBOjJ0O3ra1KUVwwXHoeqmF6OxgqW9 +DIFY_PLUGIN_DAEMON_KEY=dAX7IwPuO4ggh1MBASC25G8zfm6DVUZ5pCqKJ5vgQBguYgsSs2OVToDR +DIFY_INNER_API_KEY=7QZ0ijEkf+IAGI/38pQlopq3vhZMGPF682JBS3hSJCyjzGTRuoYZ9A5w +DIFY_WEAVIATE_API_KEY=0fd018d333fae8fb59399a9ddc6051812e06365f64c46740 +DIFY_SANDBOX_API_KEY=fd3cd878e0cefe3410a81ecaa30facdd78e1d8357c10b927 +# Dify использует общий postgres (базы dify / dify_plugin под POSTGRES_USER) +# и общий redis (db 2 для кэша, db 3 для celery-брокера). + # --- MTProto (mtg) --- # Фейк-домен для Fake-TLS — ОБЯЗАТЕЛЬНО тот же, под который сгенерирован секрет # в mtg/config.toml. Traefik матчит TLS-handshake по SNI этого домена (TCP router diff --git a/servers/ru-docker-infra/dify/ssrf_proxy/squid.conf b/servers/ru-docker-infra/dify/ssrf_proxy/squid.conf new file mode 100644 index 0000000..16f1902 --- /dev/null +++ b/servers/ru-docker-infra/dify/ssrf_proxy/squid.conf @@ -0,0 +1,52 @@ +# Dify ssrf_proxy (squid) — отрендеренный конфиг из upstream docker/ssrf_proxy/squid.conf.template. +# Значения подставлены дефолтные: HTTP_PORT=3128, REVERSE_PROXY_PORT=8194, +# SANDBOX_HOST=sandbox, SANDBOX_PORT=8194, COREDUMP_DIR=/var/spool/squid. +# Forward-proxy для api/worker/sandbox (SSRF-защита) + accel reverse-proxy на sandbox. + +acl localnet src 0.0.0.1-0.255.255.255 +acl localnet src 10.0.0.0/8 +acl localnet src 100.64.0.0/10 +acl localnet src 169.254.0.0/16 +acl localnet src 172.16.0.0/12 +acl localnet src 192.168.0.0/16 +acl localnet src fc00::/7 +acl localnet src fe80::/10 +acl SSL_ports port 443 +acl Safe_ports port 80 +acl Safe_ports port 21 +acl Safe_ports port 443 +acl Safe_ports port 70 +acl Safe_ports port 210 +acl Safe_ports port 1025-65535 +acl Safe_ports port 280 +acl Safe_ports port 488 +acl Safe_ports port 591 +acl Safe_ports port 777 +acl CONNECT method CONNECT +acl allowed_domains dstdomain .marketplace.dify.ai +http_access allow allowed_domains +http_access deny !Safe_ports +http_access deny CONNECT !SSL_ports +http_access allow localhost manager +http_access deny manager +http_access allow localhost +include /etc/squid/conf.d/*.conf +http_access deny all + +http_port 3128 +coredump_dir /var/spool/squid +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 + +http_port 8194 accel vhost +cache_peer sandbox parent 8194 0 no-query originserver +acl src_all src all +http_access allow src_all + +client_request_buffer_max_size 100 MB diff --git a/servers/ru-docker-infra/docker-compose.yml b/servers/ru-docker-infra/docker-compose.yml index c5ada24..ea6aefd 100644 --- a/servers/ru-docker-infra/docker-compose.yml +++ b/servers/ru-docker-infra/docker-compose.yml @@ -11,6 +11,10 @@ volumes: redis_data: portainer_data: gitea_data: + dify_api_storage: + dify_plugin_storage: + dify_weaviate_data: + dify_sandbox_deps: services: traefik: @@ -50,6 +54,7 @@ services: POSTGRES_DB: ${POSTGRES_DB} volumes: - postgres_data:/var/lib/postgresql/data + - ./postgres/init:/docker-entrypoint-initdb.d:ro networks: - internal healthcheck: @@ -228,3 +233,217 @@ services: - traefik.tcp.routers.mtg.entrypoints=websecure - traefik.tcp.routers.mtg.tls.passthrough=true - traefik.tcp.services.mtg.loadbalancer.server.port=3128 + + # --- Dify ----------------------------------------------------------------- + # LLM-app платформа. БД и redis — общие (postgres: база dify/dify_plugin, + # redis: db 2/3). Vector store — отдельный weaviate. Внешний вход — traefik с + # path-роутингом (вместо штатного dify-nginx): /console/api,/api,/v1,/files → api, + # /e/ → plugin_daemon, остальное → web. + + dify-api: + image: langgenius/dify-api:${DIFY_VERSION} + container_name: dify-api + restart: unless-stopped + environment: &dify-api-env + MODE: api + LOG_LEVEL: INFO + SECRET_KEY: ${DIFY_SECRET_KEY} + DEPLOY_ENV: PRODUCTION + MIGRATION_ENABLED: "true" + CONSOLE_API_URL: https://${DIFY_HOST} + CONSOLE_WEB_URL: https://${DIFY_HOST} + SERVICE_API_URL: https://${DIFY_HOST} + APP_API_URL: https://${DIFY_HOST} + APP_WEB_URL: https://${DIFY_HOST} + FILES_URL: https://${DIFY_HOST} + # --- Postgres (общий) --- + DB_USERNAME: ${POSTGRES_USER} + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_HOST: postgres + DB_PORT: "5432" + DB_DATABASE: dify + # --- Redis (общий) --- + REDIS_HOST: redis + REDIS_PORT: "6379" + REDIS_PASSWORD: ${REDIS_PASSWORD} + REDIS_DB: "2" + CELERY_BROKER_URL: redis://:${REDIS_PASSWORD}@redis:6379/3 + # --- Vector store --- + VECTOR_STORE: weaviate + WEAVIATE_ENDPOINT: http://dify-weaviate:8080 + WEAVIATE_API_KEY: ${DIFY_WEAVIATE_API_KEY} + # --- Storage (локальный том) --- + STORAGE_TYPE: opendal + OPENDAL_SCHEME: fs + OPENDAL_FS_ROOT: storage + # --- Code execution sandbox --- + CODE_EXECUTION_ENDPOINT: http://dify-sandbox:8194 + CODE_EXECUTION_API_KEY: ${DIFY_SANDBOX_API_KEY} + # --- SSRF proxy --- + SSRF_PROXY_HTTP_URL: http://dify-ssrf-proxy:3128 + SSRF_PROXY_HTTPS_URL: http://dify-ssrf-proxy:3128 + # --- Plugin daemon --- + PLUGIN_DAEMON_URL: http://dify-plugin-daemon:5002 + PLUGIN_DAEMON_KEY: ${DIFY_PLUGIN_DAEMON_KEY} + INNER_API_KEY_FOR_PLUGIN: ${DIFY_INNER_API_KEY} + MARKETPLACE_ENABLED: "true" + MARKETPLACE_API_URL: https://marketplace.dify.ai + volumes: + - dify_api_storage:/app/api/storage + networks: + - proxy + - internal + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + dify-weaviate: + condition: service_started + labels: + - traefik.enable=true + - traefik.docker.network=proxy + - traefik.http.routers.dify-api.rule=Host(`${DIFY_HOST}`) && (PathPrefix(`/console/api`) || PathPrefix(`/api`) || PathPrefix(`/v1`) || PathPrefix(`/files`)) + - traefik.http.routers.dify-api.priority=10 + - traefik.http.routers.dify-api.entrypoints=websecure + - traefik.http.routers.dify-api.tls=true + - traefik.http.routers.dify-api.tls.certresolver=le + - traefik.http.routers.dify-api.service=dify-api + - traefik.http.services.dify-api.loadbalancer.server.port=5001 + + dify-worker: + image: langgenius/dify-api:${DIFY_VERSION} + container_name: dify-worker + restart: unless-stopped + environment: + <<: *dify-api-env + MODE: worker + volumes: + - dify_api_storage:/app/api/storage + networks: + - internal + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + dify-web: + image: langgenius/dify-web:${DIFY_VERSION} + container_name: dify-web + restart: unless-stopped + environment: + CONSOLE_API_URL: https://${DIFY_HOST} + APP_API_URL: https://${DIFY_HOST} + MARKETPLACE_API_URL: https://marketplace.dify.ai + MARKETPLACE_URL: https://marketplace.dify.ai + networks: + - proxy + - internal + labels: + - traefik.enable=true + - traefik.docker.network=proxy + - traefik.http.routers.dify-web.rule=Host(`${DIFY_HOST}`) + - traefik.http.routers.dify-web.priority=1 + - traefik.http.routers.dify-web.entrypoints=websecure + - traefik.http.routers.dify-web.tls=true + - traefik.http.routers.dify-web.tls.certresolver=le + - traefik.http.routers.dify-web.service=dify-web + - traefik.http.services.dify-web.loadbalancer.server.port=3000 + + dify-plugin-daemon: + image: langgenius/dify-plugin-daemon:${DIFY_PLUGIN_DAEMON_VERSION} + container_name: dify-plugin-daemon + restart: unless-stopped + environment: + DB_USERNAME: ${POSTGRES_USER} + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_HOST: postgres + DB_PORT: "5432" + DB_DATABASE: dify_plugin + REDIS_HOST: redis + REDIS_PORT: "6379" + REDIS_PASSWORD: ${REDIS_PASSWORD} + REDIS_DB: "2" + SERVER_PORT: "5002" + SERVER_KEY: ${DIFY_PLUGIN_DAEMON_KEY} + DIFY_INNER_API_URL: http://dify-api:5001 + DIFY_INNER_API_KEY: ${DIFY_INNER_API_KEY} + PLUGIN_REMOTE_INSTALLING_HOST: 0.0.0.0 + PLUGIN_REMOTE_INSTALLING_PORT: "5003" + PLUGIN_WORKING_PATH: /app/storage/cwd + PYTHON_ENV_INIT_TIMEOUT: "120" + PLUGIN_MAX_EXECUTION_TIMEOUT: "600" + PLUGIN_STORAGE_TYPE: local + PLUGIN_STORAGE_LOCAL_ROOT: /app/storage + FORCE_VERIFYING_SIGNATURE: "true" + MARKETPLACE_ENABLED: "true" + MARKETPLACE_API_URL: https://marketplace.dify.ai + volumes: + - dify_plugin_storage:/app/storage + networks: + - proxy + - internal + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + labels: + - traefik.enable=true + - traefik.docker.network=proxy + - traefik.http.routers.dify-plugin.rule=Host(`${DIFY_HOST}`) && PathPrefix(`/e/`) + - traefik.http.routers.dify-plugin.priority=10 + - traefik.http.routers.dify-plugin.entrypoints=websecure + - traefik.http.routers.dify-plugin.tls=true + - traefik.http.routers.dify-plugin.tls.certresolver=le + - traefik.http.routers.dify-plugin.service=dify-plugin + - traefik.http.services.dify-plugin.loadbalancer.server.port=5002 + + dify-sandbox: + image: langgenius/dify-sandbox:${DIFY_SANDBOX_VERSION} + container_name: dify-sandbox + restart: unless-stopped + environment: + API_KEY: ${DIFY_SANDBOX_API_KEY} + GIN_MODE: release + WORKER_TIMEOUT: "15" + ENABLE_NETWORK: "true" + HTTP_PROXY: http://dify-ssrf-proxy:3128 + HTTPS_PROXY: http://dify-ssrf-proxy:3128 + SANDBOX_PORT: "8194" + volumes: + - dify_sandbox_deps:/dependencies + networks: + - internal + depends_on: + - dify-ssrf-proxy + + dify-ssrf-proxy: + image: ubuntu/squid:latest + container_name: dify-ssrf-proxy + restart: unless-stopped + volumes: + - ./dify/ssrf_proxy/squid.conf:/etc/squid/squid.conf:ro + networks: + - internal + + dify-weaviate: + image: semitechnologies/weaviate:1.19.0 + container_name: dify-weaviate + restart: unless-stopped + environment: + PERSISTENCE_DATA_PATH: /var/lib/weaviate + QUERY_DEFAULTS_LIMIT: "25" + AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: "false" + DEFAULT_VECTORIZER_MODULE: none + CLUSTER_HOSTNAME: node1 + AUTHENTICATION_APIKEY_ENABLED: "true" + AUTHENTICATION_APIKEY_ALLOWED_KEYS: ${DIFY_WEAVIATE_API_KEY} + AUTHENTICATION_APIKEY_USERS: dify@local + AUTHORIZATION_ADMINLIST_ENABLED: "true" + AUTHORIZATION_ADMINLIST_USERS: dify@local + volumes: + - dify_weaviate_data:/var/lib/weaviate + networks: + - internal diff --git a/servers/ru-docker-infra/postgres/init/10-dify-databases.sql b/servers/ru-docker-infra/postgres/init/10-dify-databases.sql new file mode 100644 index 0000000..1b32ad7 --- /dev/null +++ b/servers/ru-docker-infra/postgres/init/10-dify-databases.sql @@ -0,0 +1,10 @@ +-- Создание баз для Dify в общем postgres. +-- ВАЖНО: скрипты из /docker-entrypoint-initdb.d/ выполняются ТОЛЬКО при первой +-- инициализации (пустой data-каталог). На уже работающем postgres базы нужно +-- создать вручную один раз (см. servers/ru-docker-infra/README или CLAUDE.md): +-- docker exec n8n-postgres psql -U n8n -c "CREATE DATABASE dify;" +-- docker exec n8n-postgres psql -U n8n -c "CREATE DATABASE dify_plugin;" +-- Dify подключается под существующим суперпользователем postgres (POSTGRES_USER). + +SELECT 'CREATE DATABASE dify' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'dify')\gexec +SELECT 'CREATE DATABASE dify_plugin' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'dify_plugin')\gexec diff --git a/servers/ru-docker-infra/ssh/deploy_key b/servers/ru-docker-infra/ssh/deploy_key new file mode 100644 index 0000000..3ba788f --- /dev/null +++ b/servers/ru-docker-infra/ssh/deploy_key @@ -0,0 +1,25 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAqCl4Bg7SRNO/t28BlAcuO+wJaiBYRfN+fGg3h1ykuVujNKteXwnVVZze +hIc6aPOXRvDa0ssHNARSlE0SCQ7Q6rGXnoKlq7z791r6YZiJ7PQ5oCKA+J53WdB8abtO59i2 +15I1vUQffmBfuzkA3MQxhC8NJOoOVBzk7ICbEKsJGMePeieyI/iL3wi6Bwukulix9shqRysZ +i1hTN31y5Do83pIUoTnE2sk8O2mRSqpyWPyWEmvCSNZrmPxYlqUCwj61OMZhAp8Y1wVW0arj +Es3p8EY7494QYIZbENS9iWIUflmne0ms5ZHGmD8XJ83bucxnOaYu7llzrXvB46DHIHd3HwID +AQABAoIBABBE5tBLOcuf4uH30uLD6RSyCt0iIPgvjRKzMIAfle467+jGFZzDQkkEosCWpq+F +taKO5dTUAK/nJSgk5rRKoyq5oM4cnLOnVodBXYz4qYtru3GDNRAYoLpnQcom7YTfwhzMD/fu +Mc/Tuh9Obgdt0c6UkMR8AoPfwcQh0x7DNV+Ysoj9lguvUF6Y6owajoYWxOz0PlpQ6PG6QNvW +A39LV+O6mIas/Wc0uA6R4ztbsgmzxm0HVhVfesb67x68Q9iGSrrkZNadOFcMXIRJ1aofuXlX +Kbltt+tiegZ9P6Mn04F20b6WFi2arZaPl+ZWsiBAZZmsxTA9EteU1DwgXKfYgQ0CgYEA7MJm +yyhTIOwmdbhz2ITwMmOU2qPtGnKIUbFZipPTRPjzdxClc79KU3DeeAnIg+zgKM9vvWwwq6WJ +wYhMMtryaoWVPW7EfHlxAciFXze1KlJIwtsKX6WP47LFWmUPwp5+X6TUgVIjsjrgWLtMS5Ae +kmoVnOvT5UnR6k800jUqrL0CgYEAtdPzaQ+9E1Z6dLdc4ofDGZ5B5vJthygGq7PZryFPkDr0 +GZtrZKlPLM/gCUZqui5DdvCB1q8pfRIf1jI+yXOyohCYaOwC3eyfB22ga2kFQ3Cpys+jNKcV +A41UHwhSnbpgBsGmqOSw9B2HgR3Z3ZCDn/TgdWIj4oul+TbZD0vKZwsCgYEA1pmC6QRdDQtf +9PZC8u/UdCf1BZTuVjmYPEv/eG9zYSIyNCr/fQHXkfhNfgqIGKQ9rmAPzWdiWkMKsUQkpTf1 +4P3B/1QGfzjoaJBj66Gn8Xtx/kTiiz2bRe2FmuDdvy9AJttVzv4v4t4g+eyTRC6Wt2m2u2y6 +vSsDPVmb7ClKzBUCgYEAnHEfNd7PvEqkk7WMGm/awjjy1MTyzd33V1YnzlGnxh2jFkEttm8h +6xlz1ica/tN9QmGOJOuSk8S7Vaznbqa4JE6JA4VEtjK3ZUnCQ4gZejv+KrxZ3JyBLQw1O3oK +syC/LDEDtr8FLRQXGVVYtRR423ByiyyEWY/znqe9PDbmj58CgYB/wOFyktT3HcV89ZVCLGQp +HfQ6yreocCDGiUucIvfwGH8cUjclfffi6TBA1MqFTSRWjcE+tq/LjEdHWO7yG4s8Gx072U66 +KjPCoUb1QsZ/o2dJEG2yOVQbviP3KrfdZfp76SYCVx4qz0jS9vIMusBvdad6uLunMMbbBdew ++x4Bvg== +-----END RSA PRIVATE KEY----- \ No newline at end of file