networks:
  proxy:
    name: proxy
  internal:
    name: internal

volumes:
  traefik_letsencrypt:
  n8n_data:
  postgres_data:
  redis_data:
  portainer_data:
  gitea_data:
  dify_api_storage:
  dify_plugin_storage:
  dify_weaviate_data:
  dify_sandbox_deps:

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=proxy
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.le.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
      - --log.level=INFO
      - --accesslog=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_letsencrypt:/letsencrypt
    networks:
      - proxy

  postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./postgres/init:/docker-entrypoint-initdb.d:ro
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    container_name: n8n-redis
    restart: unless-stopped
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    networks:
      - internal
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 10

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    environment:
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=5678
      - N8N_LISTEN_ADDRESS=0.0.0.0
      - N8N_PROTOCOL=https
      - N8N_EDITOR_BASE_URL=https://${N8N_HOST}/
      - WEBHOOK_URL=https://${N8N_HOST}/
      - GENERIC_TIMEZONE=${TIMEZONE}
      - TZ=${TIMEZONE}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_RUNNERS_ENABLED=true
      - N8N_PROXY_HOPS=1
      - N8N_CACHE_BACKEND=redis
      - QUEUE_BULL_REDIS_HOST=redis
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
      - HTTP_PROXY=http://xray:8080
      - HTTPS_PROXY=http://xray:8080
      - NO_PROXY=localhost,127.0.0.1,postgres,redis
      - N8N_DIAGNOSTICS_ENABLED=false
      - N8N_VERSION_NOTIFICATIONS_ENABLED=false
      - N8N_TEMPLATES_ENABLED=false
      - EXTERNAL_FRONTEND_HOOKS_URLS=
      - N8N_DIAGNOSTICS_CONFIG_FRONTEND=
      - N8N_DIAGNOSTICS_CONFIG_BACKEND=
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - proxy
      - internal
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.n8n.rule=Host(`${N8N_HOST}`)
      - traefik.http.routers.n8n.entrypoints=websecure
      - traefik.http.routers.n8n.tls=true
      - traefik.http.routers.n8n.tls.certresolver=le
      - traefik.http.services.n8n.loadbalancer.server.port=5678

  tunnel:
    image: jnovack/autossh:latest
    container_name: n8n-tunnel
    restart: unless-stopped
    environment:
      SSH_REMOTE_USER: tunnel
      SSH_REMOTE_HOST: 94.247.214.20
      SSH_REMOTE_PORT: 22
      SSH_TUNNEL_PORT: 5678
      SSH_TARGET_HOST: n8n
      SSH_TARGET_PORT: 5678
      SSH_MODE: "-R"
    volumes:
      - ./ssh/tunnel_key:/id_rsa:ro
    networks:
      - internal

  xray:
    image: ghcr.io/xtls/xray-core:latest
    container_name: xray
    restart: unless-stopped
    volumes:
      - ./xray/config.json:/etc/xray/config.json:ro
    networks:
      - internal
    command: ["-config", "/etc/xray/config.json"]

  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__server__DOMAIN=${GITEA_HOST}
      - GITEA__server__ROOT_URL=https://${GITEA_HOST}/
      - GITEA__server__SSH_DOMAIN=${GITEA_HOST}
      - GITEA__server__SSH_PORT=222
      - GITEA__server__SSH_LISTEN_PORT=22
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=postgres:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=${GITEA_DB_PASSWORD}
      - GITEA__service__DISABLE_REGISTRATION=true
    volumes:
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "222:22"
    networks:
      - proxy
      - internal
    depends_on:
      postgres:
        condition: service_healthy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.gitea.rule=Host(`${GITEA_HOST}`)
      - traefik.http.routers.gitea.entrypoints=websecure
      - traefik.http.routers.gitea.tls=true
      - traefik.http.routers.gitea.tls.certresolver=le
      - traefik.http.services.gitea.loadbalancer.server.port=3000

  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    command: -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.portainer.rule=Host(`${PORTAINER_HOST}`)
      - traefik.http.routers.portainer.entrypoints=websecure
      - traefik.http.routers.portainer.tls=true
      - traefik.http.routers.portainer.tls.certresolver=le
      - traefik.http.services.portainer.loadbalancer.server.port=9000

  mtg:
    image: nineseconds/mtg:2
    container_name: mtg
    restart: unless-stopped
    volumes:
      - ./mtg/config.toml:/config.toml:ro
    networks:
      - proxy
    command: ["run", "/config.toml"]
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.tcp.routers.mtg.rule=HostSNI(`${MTG_FAKE_TLS_HOST}`)
      - 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}
      # --- Endpoint/Trigger плагины (вебхуки) ---
      # endpoint-плагины обслуживает plugin-daemon (/e/), триггеры — api (/triggers).
      # Без этого Dify генерирует webhook-URL на localhost (триггер не добавить).
      ENDPOINT_URL_TEMPLATE: https://${DIFY_HOST}/e/{hook_id}
      TRIGGER_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
      # ПРИМ.: исходящий прокси (xray) НЕ ставим на api/worker — это тормозит
      # веб-API (вызовы уходят в VLESS-туннель, блокируют воркеров gunicorn,
      # редактор отваливается по таймауту). Прокси только на plugin-daemon,
      # где крутятся LLM-провайдеры — этого достаточно для гео-разблока.
      # --- 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`) || PathPrefix(`/triggers`))
      - 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:
      # Контейнер в двух сетях (proxy+internal); без этого Next.js слушает только
      # на IP internal-сети и traefik (proxy) получает 502. Биндим все интерфейсы.
      HOSTNAME: 0.0.0.0
      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
      # --- Исходящий прокси через xray (LLM-провайдеры крутятся здесь) ---
      HTTP_PROXY: ${DIFY_HTTP_PROXY}
      HTTPS_PROXY: ${DIFY_HTTP_PROXY}
      NO_PROXY: ${DIFY_NO_PROXY}
    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

  # --- Grabcevo (статический лендинг «База Грабцево») -----------------------
  # Продающий сайт производственно-складского комплекса. Чистая статика
  # (index.html + assets/), отдаётся nginx. Домен — кириллический (IDN),
  # traefik/Let's Encrypt работают по punycode-форме из ${GRABCEVO_HOST}.
  grabcevo:
    image: nginx:alpine
    container_name: grabcevo
    restart: unless-stopped
    volumes:
      - ./grabcevo/site:/usr/share/nginx/html:ro
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.grabcevo.rule=Host(`${GRABCEVO_HOST}`) || Host(`www.${GRABCEVO_HOST}`)
      - traefik.http.routers.grabcevo.entrypoints=websecure
      - traefik.http.routers.grabcevo.tls=true
      - traefik.http.routers.grabcevo.tls.certresolver=le
      # www → apex (301)
      - traefik.http.routers.grabcevo.middlewares=grabcevo-wwwredirect
      - traefik.http.middlewares.grabcevo-wwwredirect.redirectregex.regex=^https?://www\.(.*)
      - traefik.http.middlewares.grabcevo-wwwredirect.redirectregex.replacement=https://$${1}
      - traefik.http.middlewares.grabcevo-wwwredirect.redirectregex.permanent=true
      - traefik.http.services.grabcevo.loadbalancer.server.port=80

  dify-weaviate:
    image: semitechnologies/weaviate:${DIFY_WEAVIATE_VERSION}
    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
