Some checks failed
API contract / OpenAPI type drift (push) Failing after 12m57s
관리자 /admin/ai 화면에서 provider 토큰(OAuth 액세스 토큰 또는 API 키)을 발급받아 붙여넣으면 DB에 암호화 저장되고, shared-secret으로 보호되는 게이트웨이 내부 엔드포인트로 push되어 실행 중인 엔진 컨테이너에 즉시 적용된다. NAS처럼 게이트웨이가 컨테이너로 도는 환경에서 CLI·로컬 PC 의존 없이 claude_api·openai·openrouter를 연결할 수 있다. - 게이트웨이: openrouter 어댑터 신설(chat/completions), OAuth 토큰이면 Anthropic Bearer 헤더, /internal/provider-credentials GET/POST와 boot_id 기반 재동기화 - API: app.admin_provider_credential 테이블(idempotent DDL), stdlib HMAC-CTR+MAC 암호화 서비스(PROVIDER_CREDENTIAL_SECRET, 폴백 SESSION_SECRET), /admin/providers CRUD·검증 라우트 - 웹: 제공자 연결 패널(저장·검증·해제, 토큰 힌트만 표시), 엔진 선택에 OpenRouter 추가, api.gen.ts 재생성 - compose: api 서비스에 PROVIDER_CREDENTIAL_SECRET 전달(로컬·NAS)
152 lines
7.3 KiB
YAML
152 lines
7.3 KiB
YAML
name: vignette
|
|
|
|
services:
|
|
db:
|
|
image: pgvector/pgvector:pg16
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER:-vignette_owner}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env}
|
|
POSTGRES_DB: ${POSTGRES_DB:-vignette}
|
|
APP_DB_USER: ${APP_DB_USER:-vignette_app}
|
|
APP_DB_PASSWORD: ${APP_DB_PASSWORD:?set in .env}
|
|
volumes:
|
|
- pgdata:/var/lib/postgresql/data
|
|
- ./db/init:/docker-entrypoint-initdb.d:ro
|
|
healthcheck:
|
|
test:
|
|
- CMD-SHELL
|
|
- >-
|
|
PGPASSWORD=$${APP_DB_PASSWORD} psql -U $${APP_DB_USER} -d $${POSTGRES_DB}
|
|
-tAc "SELECT to_regclass('app.ci_regression_dag_node') IS NOT NULL"
|
|
| grep -qx t
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 5m
|
|
networks: [vignette]
|
|
|
|
api:
|
|
build:
|
|
context: ..
|
|
dockerfile: apps/api/Dockerfile
|
|
args:
|
|
INSTALL_RAG: ${INSTALL_RAG:-false}
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db: { condition: service_healthy }
|
|
environment:
|
|
ENVIRONMENT: ${ENVIRONMENT:?set ENVIRONMENT in .env}
|
|
DATABASE_URL: postgresql://${APP_DB_USER:-vignette_app}:${APP_DB_PASSWORD}@db:5432/${POSTGRES_DB:-vignette}
|
|
ENGINE_URL: ${ENGINE_URL:-http://host.docker.internal:9099}
|
|
ENGINE_MODE: ${ENGINE_MODE:-claude_cli}
|
|
ENGINE_GATEWAY_SHARED_SECRET: ${ENGINE_GATEWAY_SHARED_SECRET:-}
|
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
|
OPENAI_API_KEY: ${OPENAI_API_KEY:?set in .env}
|
|
# 관리자 연결 UI(/admin/ai) provider 토큰 DB 암호화 키. 비우면 SESSION_SECRET에서 파생.
|
|
PROVIDER_CREDENTIAL_SECRET: ${PROVIDER_CREDENTIAL_SECRET:-}
|
|
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
|
VIGNETTE_VOICE_STT_PROVIDER: ${VIGNETTE_VOICE_STT_PROVIDER:-openai}
|
|
DEEPGRAM_API_KEY: ${DEEPGRAM_API_KEY:-}
|
|
DEEPGRAM_STT_URL: ${DEEPGRAM_STT_URL:-wss://api.deepgram.com/v1/listen}
|
|
DEEPGRAM_STT_MODEL: ${DEEPGRAM_STT_MODEL:-nova-3}
|
|
DEEPGRAM_STT_LANGUAGE: ${DEEPGRAM_STT_LANGUAGE:-ko}
|
|
DEEPGRAM_ENDPOINTING_MS: ${DEEPGRAM_ENDPOINTING_MS:-300}
|
|
DEEPGRAM_UTTERANCE_END_MS: ${DEEPGRAM_UTTERANCE_END_MS:-1200}
|
|
DEEPGRAM_KEEPALIVE_SECONDS: ${DEEPGRAM_KEEPALIVE_SECONDS:-4}
|
|
DEEPGRAM_FINALIZE_TIMEOUT_SECONDS: ${DEEPGRAM_FINALIZE_TIMEOUT_SECONDS:-15}
|
|
DEEPGRAM_MIP_OPT_OUT: ${DEEPGRAM_MIP_OPT_OUT:-true}
|
|
SESSION_SECRET: ${SESSION_SECRET:?set in .env}
|
|
VIGNETTE_RUPTURE_INTERNAL_TOKEN: ${VIGNETTE_RUPTURE_INTERNAL_TOKEN:?set in .env}
|
|
VIGNETTE_PRACTICE_INTERNAL_TOKEN: ${VIGNETTE_PRACTICE_INTERNAL_TOKEN:?set in .env}
|
|
VIGNETTE_CALIBRATION_TRANSFER_INTERNAL_TOKEN: ${VIGNETTE_CALIBRATION_TRANSFER_INTERNAL_TOKEN:?set in .env}
|
|
VIGNETTE_SUPERVISION_RESEARCH_INTERNAL_TOKEN: ${VIGNETTE_SUPERVISION_RESEARCH_INTERNAL_TOKEN:?set in .env}
|
|
VIGNETTE_MULTIMODAL_ALLIANCE_INTERNAL_TOKEN: ${VIGNETTE_MULTIMODAL_ALLIANCE_INTERNAL_TOKEN:?set in .env}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_INTERNAL_TOKEN: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_INTERNAL_TOKEN:?set in .env}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENABLED: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENABLED:-false}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_INTERVAL_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_INTERVAL_SECONDS:-3600}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_STARTUP_DELAY_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_STARTUP_DELAY_SECONDS:-30}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_RETRY_DELAY_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_RETRY_DELAY_SECONDS:-300}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_LEASE_TIMEOUT_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_LEASE_TIMEOUT_SECONDS:-1800}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENGINE_TIMEOUT_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENGINE_TIMEOUT_SECONDS:-300}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_BATCH_SIZE: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_BATCH_SIZE:-1}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_DRIFT_TRIGGER_ENABLED: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_DRIFT_TRIGGER_ENABLED:-false}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENABLED: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENABLED:-false}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENDPOINT: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENDPOINT:-}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_TOKEN: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_TOKEN:-}
|
|
VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_TIMEOUT_SECONDS: ${VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_TIMEOUT_SECONDS:-30}
|
|
OAUTH_GOOGLE_CLIENT_ID: ${OAUTH_GOOGLE_CLIENT_ID:?set in .env}
|
|
OAUTH_GOOGLE_CLIENT_SECRET: ${OAUTH_GOOGLE_CLIENT_SECRET:?set in .env}
|
|
OAUTH_REDIRECT_URI: ${OAUTH_REDIRECT_URI:-https://api-vignette.chanpaca.net/auth/callback}
|
|
AUTH_ALLOWED_EMAIL_DOMAINS: '${AUTH_ALLOWED_EMAIL_DOMAINS:-["hs.ac.kr","twentyoz.kr"]}'
|
|
AUTH_TEACHER_EMAILS: '${AUTH_TEACHER_EMAILS:-[]}'
|
|
AUTH_ADMIN_EMAILS: '${AUTH_ADMIN_EMAILS:-[]}'
|
|
AUTH_DEV_LOGIN_ENABLED: ${AUTH_DEV_LOGIN_ENABLED:-false}
|
|
AUTH_DEV_LOGIN_EXTRA_ORIGINS: '${AUTH_DEV_LOGIN_EXTRA_ORIGINS:-[]}'
|
|
DEFAULT_AFFILIATION: ${DEFAULT_AFFILIATION:-}
|
|
AUTO_SEED_PERSONAS: ${AUTO_SEED_PERSONAS:-false}
|
|
ALLOW_SEED_PERSONA_FALLBACK: ${ALLOW_SEED_PERSONA_FALLBACK:-false}
|
|
EVALUATOR_GOLDEN_FEWSHOT_ENABLED: ${EVALUATOR_GOLDEN_FEWSHOT_ENABLED:-false}
|
|
FRONTEND_BASE_URL: ${FRONTEND_BASE_URL:-https://vignette.chanpaca.net}
|
|
FRONTEND_ORIGIN_MAP: '${FRONTEND_ORIGIN_MAP:?set FRONTEND_ORIGIN_MAP in .env}'
|
|
CORS_ORIGINS: '${CORS_ORIGINS:-["https://vignette.chanpaca.net","https://vnet.18ka.net","https://vignette-b1q.pages.dev"]}'
|
|
USER_UPLOAD_DIR: ${USER_UPLOAD_DIR:-/app/uploads}
|
|
NOTIFICATION_EMAIL_PROVIDER: ${NOTIFICATION_EMAIL_PROVIDER:-disabled}
|
|
NOTIFICATION_EMAIL_MAX_ATTEMPTS: ${NOTIFICATION_EMAIL_MAX_ATTEMPTS:-3}
|
|
NOTIFICATION_EMAIL_RETRY_SECONDS: ${NOTIFICATION_EMAIL_RETRY_SECONDS:-900}
|
|
SMTP_HOST: ${SMTP_HOST:-}
|
|
SMTP_PORT: ${SMTP_PORT:-587}
|
|
SMTP_USERNAME: ${SMTP_USERNAME:-}
|
|
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
|
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-}
|
|
SMTP_FROM_NAME: ${SMTP_FROM_NAME:-Vignette}
|
|
SMTP_STARTTLS: ${SMTP_STARTTLS:-true}
|
|
SMTP_SSL: ${SMTP_SSL:-false}
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
volumes:
|
|
- apiuploads:/app/uploads
|
|
healthcheck:
|
|
test:
|
|
- CMD
|
|
- python
|
|
- -c
|
|
- >-
|
|
import json, urllib.request;
|
|
payload=json.load(urllib.request.urlopen('http://localhost:8000/health'));
|
|
assert payload.get('status') == 'ok' and payload.get('db') is True and payload.get('engine') is True
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
networks: [vignette]
|
|
|
|
web:
|
|
build:
|
|
context: ../apps/web
|
|
dockerfile: Dockerfile
|
|
args:
|
|
VITE_API_BASE: ${PUBLIC_API_BASE:-/api}
|
|
restart: unless-stopped
|
|
depends_on: [api]
|
|
networks: [vignette]
|
|
|
|
proxy:
|
|
image: caddy:2-alpine
|
|
restart: unless-stopped
|
|
depends_on: [web, api]
|
|
ports:
|
|
- "${HTTP_PORT:-8080}:80"
|
|
- "${HTTPS_PORT:-8443}:443"
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- caddydata:/data
|
|
networks: [vignette]
|
|
|
|
volumes:
|
|
pgdata:
|
|
apiuploads:
|
|
caddydata:
|
|
|
|
networks:
|
|
vignette:
|
|
driver: bridge
|