ac-tool/docker-compose.yml
Vadym Samoilenko 8da149b84e Migrate storage from JSON files to PostgreSQL (asyncpg)
- Add asyncpg connection pool (db/pool.py) with JSONB codec registration
- Add schema.sql with users, clients, dropdown_categories, export_templates, sheets tables
- Add migrate_json.py one-time migration script for existing JSON data
- Rewrite user_store, sheets/manager, api/clients, api/dropdowns, api/export as async DB-backed
- Update all callers (auth, sheets, admin, ai_command, export) to await async functions
- Add postgres:16-alpine service to docker-compose with named volume and health check
- App container depends_on postgres; DATABASE_URL injected via env
- Schema applied automatically on startup; global categories seeded if DB is empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 19:51:37 +00:00

139 lines
5.2 KiB
YAML

# Apache reverse proxy config (add inside your VirtualHost block):
#
# # Required modules: a2enmod proxy proxy_http proxy_wstunnel
#
# # Proxy API requests to the Docker container
# <Location /ac-helper/api/>
# ProxyPass http://localhost:8100/api/
# ProxyPassReverse http://localhost:8100/api/
# </Location>
#
# # 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/
# <Directory /var/www/html/ac-helper>
# Options -Indexes
# AllowOverride None
# Require all granted
# FallbackResource /ac-helper/index.html
# </Directory>
#
# 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: