From 6edc54c1f1d4587da005dc94f2e75357ecbd059a Mon Sep 17 00:00:00 2001 From: cocoyoon Date: Wed, 8 Apr 2026 14:42:32 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EB=B0=B1=EC=97=94=EB=93=9C=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=ED=99=98=EA=B2=BD=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=EB=8B=A8=EC=9D=BC=20.env.backend.{env}=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit api-server/.env.{env} + ai-server/.{env}.env 2개 파일을 루트 .env.backend.{env} 1개로 통합하여 관리 포인트를 줄임. - .env.backend.example 추가 (통합 템플릿) - docker-compose 3개 (dev/staging/prod) env_file 경로 통합 - deploy-backend.sh env 파일 체크 단순화 - 충돌 키 LOG_FORMAT은 compose environment에서 서비스별 오버라이드 Closes #118 Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.backend.example | 94 +++++++++++++++++++ .gitignore | 3 + packages/api-server/docker/stack/README.md | 6 +- .../docker/stack/docker-compose.prod.yml | 12 ++- .../docker/stack/docker-compose.staging.yml | 12 ++- .../docker/stack/docker-compose.yml | 12 ++- scripts/deploy-backend.sh | 37 +++----- 7 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 .env.backend.example diff --git a/.env.backend.example b/.env.backend.example new file mode 100644 index 00000000..45be526b --- /dev/null +++ b/.env.backend.example @@ -0,0 +1,94 @@ +# Decoded Backend — unified env file for Docker stack (api + ai + meilisearch + redis + searxng) +# Usage: cp .env.backend.example .env.backend.prod (or .env.backend.dev / .env.backend.staging) +# +# Docker Compose overrides internal service URLs automatically (MEILISEARCH_URL, REDIS_HOST, etc.) +# You only need to fill in external service credentials below. + +# ─── Shared ─────────────────────────────────────────────── +API_SERVER_GRPC_PORT=50053 + +# ─── API Server (Rust/Axum) ────────────────────────────── +ENV=production +HOST=0.0.0.0 +PORT=8000 +RUST_LOG=info +LOG_FORMAT=text +ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173 + +# Database (Supabase PostgreSQL) +DATABASE_URL=postgresql://postgres:[PASSWORD]@[PROJECT-REF].supabase.co:5432/postgres +DB_MAX_CONNECTIONS=100 +DB_MIN_CONNECTIONS=5 +DB_CONNECT_TIMEOUT=30 +DB_IDLE_TIMEOUT=600 + +# Supabase Auth +SUPABASE_URL=https://[PROJECT-REF].supabase.co +SUPABASE_ANON_KEY=eyJhbGc... +SUPABASE_SERVICE_ROLE_KEY=eyJhbGc... +SUPABASE_JWT_SECRET=your-jwt-secret + +# Meilisearch +MEILISEARCH_URL=http://localhost:7700 +MEILISEARCH_MASTER_KEY=your-master-key + +# Cloudflare R2 +R2_ACCOUNT_ID=your-account-id +R2_ACCESS_KEY_ID=your-access-key +R2_SECRET_ACCESS_KEY=your-secret-key +R2_BUCKET_NAME=decoded-images +R2_PUBLIC_URL=https://pub-xxxxx.r2.dev + +# Affiliate +RAKUTEN_API_KEY=your-rakuten-key +RAKUTEN_PUBLISHER_ID=your-publisher-id + +# AI Queue gRPC (API -> ai-server) +AI_SERVER_GRPC_URL=http://localhost:50052 + +# Vector Search (OpenAI Embeddings) +OPENAI_API_KEY=sk-... +OPENAI_EMBEDDING_MODEL=text-embedding-3-small +OPENAI_EMBEDDING_DIMENSIONS=256 + +# ─── AI Server (Python/gRPC) ───────────────────────────── +APP_ENV=prod + +JWT_SECRET_KEY=your-jwt-secret-change-me +JWT_ALGORITHM=HS256 + +LOG_LEVEL=INFO +# Note: LOG_FORMAT is set per-service in docker-compose (api=text, ai=json) + +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD=your-redis-password +REDIS_DB=0 + +QUEUE_BATCH_SIZE=10 + +API_SERVER_HTTP_URL=http://localhost:8000 +API_SERVER_ACCESS_TOKEN=your_backend_token + +SELENIUM_URL=http://localhost:4444 + +API_SERVER_GRPC_HOST=localhost + +PERPLEXITY_API_KEY= +PERPLEXITY_API_URL=https://api.perplexity.ai +PERPLEXITY_MODEL=sonar +PERPLEXITY_MAX_RETRIES=3 +PERPLEXITY_REQUEST_TIMEOUT=30 + +SEARXNG_API_URL=http://localhost:4000 +SEARXNG_MAX_RETRIES=3 +SEARXNG_REQUEST_TIMEOUT=10 + +TELEGRAM_BOT_TOKEN= +TELEGRAM_CHAT_ID= +TELEGRAM_ENABLED=false + +BATCH_SIZE=10 +MAX_CONCURRENT_REQUESTS=5 +REQUEST_TIMEOUT=30 +MAX_RETRIES=3 diff --git a/.gitignore b/.gitignore index 52a19950..7d7000e8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ node_modules/ .env .env.local .env.*.local +.env.backend.dev +.env.backend.staging +.env.backend.prod # Next.js .next/ diff --git a/packages/api-server/docker/stack/README.md b/packages/api-server/docker/stack/README.md index ac3c125a..e5f33222 100644 --- a/packages/api-server/docker/stack/README.md +++ b/packages/api-server/docker/stack/README.md @@ -4,7 +4,7 @@ ## Build / run (모노레포 루트) -1. `packages/api-server/.env.dev` + `packages/ai-server/.dev.env` 준비 (staging/prod는 각각 `.env.staging` / `.staging.env`, `.env.prod` / `.prod.env`). +1. 모노레포 루트에 `.env.backend.dev` 준비 (staging: `.env.backend.staging`, prod: `.env.backend.prod`). 템플릿: `.env.backend.example`. 2. 배포 스크립트: ```bash @@ -35,13 +35,13 @@ Compose `environment`로 덮어쓰는 값: `MEILISEARCH_URL`, `AI_SERVER_GRPC_UR ## 수동 compose ```bash -docker compose --env-file packages/api-server/.env.dev \ +docker compose --env-file .env.backend.dev \ -f packages/api-server/docker/stack/docker-compose.yml up --build ``` ## Meilisearch 키 -`deploy-backend.sh`는 API env를 `--env-file`로 넘겨 `${MEILISEARCH_MASTER_KEY}` 보간에 사용합니다. prod는 `packages/api-server/.env.prod`에 키가 있어야 합니다. +`deploy-backend.sh`는 통합 env를 `--env-file`로 넘겨 `${MEILISEARCH_MASTER_KEY}` 보간에 사용합니다. prod는 `.env.backend.prod`에 키가 있어야 합니다. ## 환경 변수 이름 diff --git a/packages/api-server/docker/stack/docker-compose.prod.yml b/packages/api-server/docker/stack/docker-compose.prod.yml index b1184a0f..63c7f8d6 100644 --- a/packages/api-server/docker/stack/docker-compose.prod.yml +++ b/packages/api-server/docker/stack/docker-compose.prod.yml @@ -1,7 +1,7 @@ # Production-style: api + ai + Meilisearch + Redis + SearXNG (Meili/Redis/SearXNG not published to host). -# Env: packages/api-server/.env.prod + packages/ai-server/.prod.env +# Env: .env.backend.prod (repo root, unified) # From repo root: bash scripts/deploy-backend.sh prod up -# Set MEILISEARCH_MASTER_KEY in packages/api-server/.env.prod (used with deploy-backend.sh --env-file). +# Set MEILISEARCH_MASTER_KEY in .env.backend.prod (used with deploy-backend.sh --env-file). name: decoded-backend-prod @@ -15,10 +15,11 @@ services: ports: - "8080:8080" env_file: - - ../../.env.prod + - ../../../../.env.backend.prod environment: PORT: "8080" HOST: "0.0.0.0" + LOG_FORMAT: text MEILISEARCH_URL: http://meilisearch:7700 AI_SERVER_GRPC_URL: http://ai:50051 API_SERVER_GRPC_PORT: "50052" @@ -44,8 +45,9 @@ services: expose: - "50051" env_file: - - ../../../ai-server/.prod.env + - ../../../../.env.backend.prod environment: + LOG_FORMAT: json REDIS_HOST: redis REDIS_PORT: "6379" SEARXNG_API_URL: http://searxng:8080 @@ -95,7 +97,7 @@ services: networks: - decoded-backend-prod env_file: - - ../../../ai-server/.prod.env + - ../../../../.env.backend.prod command: - /bin/sh - -c diff --git a/packages/api-server/docker/stack/docker-compose.staging.yml b/packages/api-server/docker/stack/docker-compose.staging.yml index 31bb0dbc..0839aa93 100644 --- a/packages/api-server/docker/stack/docker-compose.staging.yml +++ b/packages/api-server/docker/stack/docker-compose.staging.yml @@ -1,5 +1,5 @@ # Staging: api + ai + Meilisearch + Redis + SearXNG -# Env: packages/api-server/.env.staging + packages/ai-server/.staging.env +# Env: .env.backend.staging (repo root, unified) # From repo root: bash scripts/deploy-backend.sh staging up name: decoded-backend-staging @@ -14,10 +14,11 @@ services: ports: - "8081:8080" env_file: - - ../../.env.staging + - ../../../../.env.backend.staging environment: PORT: "8080" HOST: "0.0.0.0" + LOG_FORMAT: text MEILISEARCH_URL: http://meilisearch:7700 AI_SERVER_GRPC_URL: http://ai:50051 API_SERVER_GRPC_PORT: "50052" @@ -43,8 +44,9 @@ services: expose: - "50051" env_file: - - ../../../ai-server/.staging.env + - ../../../../.env.backend.staging environment: + LOG_FORMAT: json REDIS_HOST: redis REDIS_PORT: "6379" SEARXNG_API_URL: http://searxng:8080 @@ -52,7 +54,7 @@ services: API_SERVER_GRPC_HOST: api API_SERVER_GRPC_PORT: "50052" AI_GRPC_LISTEN_PORT: "50051" - APP_ENV: prod + APP_ENV: staging ENV: staging depends_on: api: @@ -98,7 +100,7 @@ services: networks: - decoded-backend-staging env_file: - - ../../../ai-server/.staging.env + - ../../../../.env.backend.staging command: - /bin/sh - -c diff --git a/packages/api-server/docker/stack/docker-compose.yml b/packages/api-server/docker/stack/docker-compose.yml index 9c2c5e16..79159536 100644 --- a/packages/api-server/docker/stack/docker-compose.yml +++ b/packages/api-server/docker/stack/docker-compose.yml @@ -1,6 +1,6 @@ # Decoded backend: api + ai + Meilisearch + Redis + SearXNG (multi-container). # From repo root: bash scripts/deploy-backend.sh dev up --build -# Requires packages/api-server/.env.dev + packages/ai-server/.dev.env +# Requires .env.backend.dev (repo root, unified) name: decoded-backend @@ -14,10 +14,11 @@ services: ports: - "8080:8080" env_file: - - ../../.env.dev + - ../../../../.env.backend.dev environment: PORT: "8080" HOST: "0.0.0.0" + LOG_FORMAT: text MEILISEARCH_URL: http://meilisearch:7700 AI_SERVER_GRPC_URL: http://ai:50051 API_SERVER_GRPC_PORT: "50052" @@ -44,8 +45,9 @@ services: expose: - "50051" env_file: - - ../../../ai-server/.dev.env + - ../../../../.env.backend.dev environment: + LOG_FORMAT: json REDIS_HOST: redis REDIS_PORT: "6379" SEARXNG_API_URL: http://searxng:8080 @@ -53,7 +55,7 @@ services: API_SERVER_GRPC_HOST: api API_SERVER_GRPC_PORT: "50052" AI_GRPC_LISTEN_PORT: "50051" - APP_ENV: prod + APP_ENV: dev depends_on: api: condition: service_healthy @@ -98,7 +100,7 @@ services: networks: - decoded-backend env_file: - - ../../../ai-server/.dev.env + - ../../../../.env.backend.dev command: - /bin/sh - -c diff --git a/scripts/deploy-backend.sh b/scripts/deploy-backend.sh index a34a8b9b..a6d2b4b3 100755 --- a/scripts/deploy-backend.sh +++ b/scripts/deploy-backend.sh @@ -11,13 +11,13 @@ # 액션: up | down | build | pull | ps | logs | restart | config # 기본 액션: up -d # -# 필수 env 파일: -# dev: packages/api-server/.env.dev + packages/ai-server/.dev.env -# staging: packages/api-server/.env.staging + packages/ai-server/.staging.env -# prod: packages/api-server/.env.prod + packages/ai-server/.prod.env +# 필수 env 파일 (모노레포 루트): +# dev: .env.backend.dev +# staging: .env.backend.staging +# prod: .env.backend.prod # -# Meilisearch: compose의 ${MEILISEARCH_MASTER_KEY}는 API env 파일로 보간됨 (--env-file). -# prod는 .env.prod에 MEILISEARCH_MASTER_KEY 필수(:?). +# Meilisearch: compose의 ${MEILISEARCH_MASTER_KEY}는 env 파일로 보간됨 (--env-file). +# prod는 .env.backend.prod에 MEILISEARCH_MASTER_KEY 필수(:?). set -euo pipefail @@ -44,18 +44,15 @@ EXTRA=("$@") case "$ENV" in dev) COMPOSE="$STACK/docker-compose.yml" - API_ENV="$ROOT/packages/api-server/.env.dev" - AI_ENV="$ROOT/packages/ai-server/.dev.env" + ENV_FILE="$ROOT/.env.backend.dev" ;; staging) COMPOSE="$STACK/docker-compose.staging.yml" - API_ENV="$ROOT/packages/api-server/.env.staging" - AI_ENV="$ROOT/packages/ai-server/.staging.env" + ENV_FILE="$ROOT/.env.backend.staging" ;; prod) COMPOSE="$STACK/docker-compose.prod.yml" - API_ENV="$ROOT/packages/api-server/.env.prod" - AI_ENV="$ROOT/packages/ai-server/.prod.env" + ENV_FILE="$ROOT/.env.backend.prod" ;; *) usage @@ -63,16 +60,8 @@ case "$ENV" in esac require_env_files() { - local missing=0 - if [[ ! -f "$API_ENV" ]]; then - echo "Missing: $API_ENV (copy from packages/api-server/.env.dev.example or sibling)" >&2 - missing=1 - fi - if [[ ! -f "$AI_ENV" ]]; then - echo "Missing: $AI_ENV (copy from packages/ai-server/.dev.env.example or sibling)" >&2 - missing=1 - fi - if [[ "$missing" -ne 0 ]]; then + if [[ ! -f "$ENV_FILE" ]]; then + echo "Missing: $ENV_FILE (copy from .env.backend.example)" >&2 exit 1 fi } @@ -81,8 +70,8 @@ compose() { # --env-file: ${MEILISEARCH_MASTER_KEY} 등 compose 파일 내 보간용 (컨테이너 전체에 노출되지 않음) # set -u + 빈 EXTRA[@]는 일부 bash(예: macOS 3.2)에서 실패하므로 배열로 합쳐서 실행 local -a cmd - if [[ -f "$API_ENV" ]]; then - cmd=(docker compose --env-file "$API_ENV" -f "$COMPOSE" "$@") + if [[ -f "$ENV_FILE" ]]; then + cmd=(docker compose --env-file "$ENV_FILE" -f "$COMPOSE" "$@") else cmd=(docker compose -f "$COMPOSE" "$@") fi