# Open WebUI as a plain chat front end for LiteLLM. No login (WEBUI_AUTH=False)
# on purpose, so the only thing standing between a visitor and an admin session
# is Traefik's IP allow list. Everything the UI could do beyond chatting is
# switched off. Secrets and the hostname come from .env (see env.example).
name: open-webui

services:
  open-webui:
    # Pin the version, like the rest of the host. Releases:
    # https://github.com/open-webui/open-webui/releases
    image: ghcr.io/open-webui/open-webui:v0.11.0
    container_name: open-webui
    # No published ports; Traefik is the only way in.
    volumes:
      - ./data:/app/backend/data
    env_file:
      - .env
    environment:
      # This file is the configuration, not the database. Variables marked
      # PersistentConfig are normally read from env only on first start and
      # then overridden by whatever is in data/webui.db. Turning persistent
      # config off makes env win every time; the price is that admin-UI edits
      # do not survive a restart, so settings are changed here.
      ENABLE_PERSISTENT_CONFIG: "False"

      # --- the only source of models: LiteLLM ---
      ENABLE_OPENAI_API: "True"
      OPENAI_API_BASE_URL: http://litellm:4000/v1
      # No Ollama here; otherwise the UI waits for a timeout on start.
      ENABLE_OLLAMA_API: "False"
      # Users may not add their own endpoints around the proxy.
      ENABLE_DIRECT_CONNECTIONS: "False"
      ENABLE_EVALUATION_ARENA_MODELS: "False"

      # --- no local models ---
      # RAG_EMBEDDING_ENGINE="" (the default) makes the backend load
      # sentence-transformers into RAM at start, AUDIO_STT_ENGINE="" pulls a
      # local whisper. Neither feature is used; the engines are pointed at
      # "openai" so no local weights load under any circumstances.
      RAG_EMBEDDING_ENGINE: openai
      AUDIO_STT_ENGINE: openai

      # --- everything that is not chat is off ---
      ENABLE_WEB_SEARCH: "False"
      ENABLE_IMAGE_GENERATION: "False"
      ENABLE_CODE_EXECUTION: "False"       # default True
      ENABLE_CODE_INTERPRETER: "False"     # default True
      ENABLE_COMMUNITY_SHARING: "False"    # default True; talks to an external site
      ENABLE_CHANNELS: "False"
      ENABLE_AUTOMATIONS: "False"
      ENABLE_CALENDAR: "False"
      ENABLE_SUBAGENTS: "False"
      ENABLE_API_KEYS: "False"
      ENABLE_GOOGLE_DRIVE_INTEGRATION: "False"
      ENABLE_ONEDRIVE_INTEGRATION: "False"

      # --- extra model calls ---
      # Each of these is one more request to LiteLLM on top of the chat
      # itself. On a free quota (analyst: 8 rpm) that is noticeable.
      ENABLE_TAGS_GENERATION: "False"        # default True
      ENABLE_FOLLOW_UP_GENERATION: "False"   # default True
      ENABLE_AUTOCOMPLETE_GENERATION: "False"
      ENABLE_RETRIEVAL_QUERY_GENERATION: "False"
      ENABLE_SEARCH_QUERY_GENERATION: "False"
      # Chat titles kept: one request per conversation, part of normal chat UX.
      ENABLE_TITLE_GENERATION: "True"

      # --- what users can do in the interface ---
      USER_PERMISSIONS_CHAT_FILE_UPLOAD: "False"
      USER_PERMISSIONS_CHAT_WEB_UPLOAD: "False"
      USER_PERMISSIONS_CHAT_STT: "False"
      USER_PERMISSIONS_CHAT_TTS: "False"
      USER_PERMISSIONS_CHAT_CALL: "False"
      USER_PERMISSIONS_FEATURES_WEB_SEARCH: "False"
      USER_PERMISSIONS_FEATURES_IMAGE_GENERATION: "False"
      USER_PERMISSIONS_FEATURES_CODE_INTERPRETER: "False"
      USER_PERMISSIONS_FEATURES_NOTES: "False"
      USER_PERMISSIONS_FEATURES_MEMORIES: "False"
      USER_PERMISSIONS_FEATURES_CHANNELS: "False"
      USER_PERMISSIONS_FEATURES_AUTOMATIONS: "False"
      USER_PERMISSIONS_FEATURES_CALENDAR: "False"
      USER_PERMISSIONS_FEATURES_API_KEYS: "False"
      USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS: "False"
      USER_PERMISSIONS_FEATURES_USER_WEBHOOKS: "False"
      USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ACCESS: "False"
      USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS: "False"
      USER_PERMISSIONS_WORKSPACE_TOOLS_ACCESS: "False"
      USER_PERMISSIONS_WORKSPACE_PROMPTS_ACCESS: "False"
      USER_PERMISSIONS_WORKSPACE_SKILLS_ACCESS: "False"

      # --- misc ---
      # Only our own origin instead of the default "*".
      CORS_ALLOW_ORIGIN: https://${OPENWEBUI_DOMAIN}
      WEBUI_URL: https://${OPENWEBUI_DOMAIN}
      # Authentication off: no login form; Open WebUI creates admin@localhost
      # and signs every visitor into it. Works ONLY on a clean install: with
      # any other user in the DB every request gets HTTP 400 (routers/auths.py,
      # ERROR_MESSAGES.EXISTING_USERS). To bring the login back it is not
      # enough to flip the flag; ./data must be wiped as well.
      # ENABLE_SIGNUP is not set here: with WEBUI_AUTH=False config.py forces
      # it off.
      WEBUI_AUTH: "False"
    networks:
      # The litellm stack's network: the proxy is reachable by name as
      # `litellm`. It is internal (no route out); internet comes via traefik.
      # .82 litellm, .83 litellm-db, .84 hermes: take the next free address.
      litellm-internal:
        ipv4_address: 10.0.4.85
      traefik:
        ipv4_address: 10.0.4.37
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.open-webui.rule=Host(`${OPENWEBUI_DOMAIN}`)"
      - "traefik.http.routers.open-webui.entrypoints=websecure"
      - "traefik.http.routers.open-webui.tls=true"
      - "traefik.http.routers.open-webui.tls.certresolver=${TRAEFIK_CERTRESOLVER:-letsencrypt}"
      - "traefik.http.services.open-webui.loadbalancer.server.port=8080"
      - "traefik.http.services.open-webui.loadbalancer.server.scheme=http"
      # --- access only from these ranges ---
      # Deliberately narrow: there is no login, so anyone who reaches the
      # address is admin. The range is "who may open the chat at all".
      # NOTE: VPN clients live in 10.10.10.0/24 and are NOT included; add them
      # as a third entry if you want the chat over VPN.
      # depth=1 is mandatory: traffic comes through the front proxy 10.0.12.30,
      # and without it the list would be checked against the proxy's address
      # and admit everyone.
      - "traefik.http.middlewares.open-webui-internal.ipallowlist.sourcerange=10.0.8.0/22,10.0.12.0/22"
      - "traefik.http.middlewares.open-webui-internal.ipallowlist.ipstrategy.depth=1"
      - "traefik.http.routers.open-webui.middlewares=open-webui-internal,robots-tag-header@file"
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 60s
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

networks:
  # Neither network is created by this stack: litellm-internal comes from the
  # litellm compose, traefik from the traefik stack. Start those first.
  litellm-internal:
    external: true
  traefik:
    external: true
