# Apache reverse proxy config (add inside your VirtualHost block): # # # Required modules: a2enmod proxy proxy_http proxy_wstunnel # # # Proxy API requests to the Docker container # # ProxyPass http://localhost:8100/api/ # ProxyPassReverse http://localhost:8100/api/ # # # # Proxy WebSocket # ProxyPass /ac-helper/ws ws://localhost:8100/ws # ProxyPassReverse /ac-helper/ws ws://localhost:8100/ws # # # Serve frontend static files directly from disk # Alias /ac-helper/ /var/www/html/ac-helper/ # # Options -Indexes # AllowOverride None # Require all granted # FallbackResource /ac-helper/index.html # # # Apache serves static files; Docker handles /api and /ws only. # APP_PORT in .env controls the host port (default: 8100). version: '3.9' services: postgres: image: postgres:16-alpine container_name: ac-tool-db restart: unless-stopped environment: POSTGRES_DB: achelper POSTGRES_USER: achelper POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-achelper_secret} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U achelper -d achelper"] interval: 10s timeout: 5s retries: 5 app: build: . container_name: ac-tool restart: unless-stopped depends_on: postgres: condition: service_healthy ports: - "${APP_PORT:-8100}:8000" volumes: - ./data:/app/data environment: DATABASE_URL: postgresql://achelper:${POSTGRES_PASSWORD:-achelper_secret}@postgres:5432/achelper # Auth — AZURE_* names in .env, MSAL_* names read by server/config_runtime.py AZURE_TENANT_ID: ${AZURE_TENANT_ID:-e519c2e6-bc6d-4fdf-8d9c-923c2f002385} AZURE_CLIENT_ID: ${AZURE_CLIENT_ID:-9079054c-9620-4757-a256-23413042f1ef} AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-https://ai-sandbox.oliver.solutions/ac-helper/} MSAL_TENANT_ID: ${AZURE_TENANT_ID:-e519c2e6-bc6d-4fdf-8d9c-923c2f002385} MSAL_CLIENT_ID: ${AZURE_CLIENT_ID:-9079054c-9620-4757-a256-23413042f1ef} MSAL_REDIRECT_URI: ${AZURE_REDIRECT_URI:-https://ai-sandbox.oliver.solutions/ac-helper/} SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production} # Dev mode (set to false in production) DEV_MODE: ${DEV_MODE:-false} DEV_USER_ID: ${DEV_USER_ID:-dev-user-001} DEV_USER_ROLE: ${DEV_USER_ROLE:-admin} # Admin bootstrap ADMIN_EMAIL: ${ADMIN_EMAIL:-daveporter@oliver.agency} ADMIN_EMAILS: ${ADMIN_EMAILS:-daveporter@oliver.agency,vadymsamoilenko@oliver.agency} # Emergency access (bypass SSO) — set a long random string to enable EMERGENCY_TOKEN: ${EMERGENCY_TOKEN:-} EMERGENCY_USER_EMAIL: ${EMERGENCY_USER_EMAIL:-daveporter@oliver.agency} EMERGENCY_USER_NAME: ${EMERGENCY_USER_NAME:-Emergency Access} # OpenAI OPENAI_API_KEY: ${OPENAI_API_KEY:-} OPENAI_MODEL: ${OPENAI_MODEL:-gpt-4.1} OPENAI_REASONING_EFFORT: ${OPENAI_REASONING_EFFORT:-medium} OPENAI_TIMEOUT: ${OPENAI_TIMEOUT:-3600} OPENAI_MAX_RETRIES: ${OPENAI_MAX_RETRIES:-2} # Google Gemini GEMINI_API_KEY: ${GEMINI_API_KEY:-} GEMINI_MODEL: ${GEMINI_MODEL:-gemini-3-flash-preview} GOOGLE_MODEL: ${GOOGLE_MODEL:-gemini-3.1-pro-preview} GOOGLE_TEMPERATURE: ${GOOGLE_TEMPERATURE:-0.7} GOOGLE_MAX_OUTPUT_TOKENS: ${GOOGLE_MAX_OUTPUT_TOKENS:-100000} GOOGLE_THINKING_BUDGET: ${GOOGLE_THINKING_BUDGET:-12000} GOOGLE_TIMEOUT: ${GOOGLE_TIMEOUT:-3600} # Anthropic ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} ANTHROPIC_MODEL_OPUS: ${ANTHROPIC_MODEL_OPUS:-claude-opus-4-5-20251101} ANTHROPIC_MODEL_SONNET: ${ANTHROPIC_MODEL_SONNET:-claude-sonnet-4-5-20250929} ANTHROPIC_TEMPERATURE: ${ANTHROPIC_TEMPERATURE:-1} ANTHROPIC_MAX_TOKENS: ${ANTHROPIC_MAX_TOKENS:-32000} ANTHROPIC_THINKING_BUDGET: ${ANTHROPIC_THINKING_BUDGET:-12000} ANTHROPIC_TIMEOUT: ${ANTHROPIC_TIMEOUT:-300} # LlamaCloud LLAMA_CLOUD_API_KEY: ${LLAMA_CLOUD_API_KEY:-} # Brief extraction DEFAULT_PRIMARY_MODELS: ${DEFAULT_PRIMARY_MODELS:-anthropic-sonnet45,google-gemini20} DEFAULT_CONSOLIDATION_MODEL: ${DEFAULT_CONSOLIDATION_MODEL:-anthropic-sonnet45} MINIMUM_SUCCESS_THRESHOLD: ${MINIMUM_SUCCESS_THRESHOLD:-1} ENABLE_COST_ESTIMATION: ${ENABLE_COST_ESTIMATION:-true} MAX_PROCESSING_COST_USD: ${MAX_PROCESSING_COST_USD:-10.00} MAX_CONCURRENT_JOBS: ${MAX_CONCURRENT_JOBS:-5} # File upload MAX_UPLOAD_SIZE_MB: ${MAX_UPLOAD_SIZE_MB:-200} FILE_RETENTION_HOURS: ${FILE_RETENTION_HOURS:-24} WS_PING_INTERVAL_SECONDS: ${WS_PING_INTERVAL_SECONDS:-30} # Paths DATA_DIR: /app/data UPLOADS_DIR: /app/data/uploads OUTPUTS_DIR: /app/data/outputs SHEETS_DIR: /app/data/sheets USERS_FILE: /app/data/users.json DROPDOWNS_FILE: /app/data/dropdowns.json healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] interval: 30s timeout: 10s retries: 3 start_period: 30s volumes: postgres_data: