From 44756777635cda431a06dff0ff2452f04e011b14 Mon Sep 17 00:00:00 2001 From: Xiaoxia AI Date: Mon, 15 Jun 2026 18:40:56 +0800 Subject: [PATCH] chore: wire deployment to environment-based configuration --- .env.staging | 4 +- .gitea/workflows/deploy.yml | 95 +++++++++----------- apps/api/app/core/database.py | 32 ++++++- apps/api/app/db.py | 8 +- apps/worker/worker_app/core/config.py | 28 +++++- docs/DEPLOYMENT.md | 123 ++++++++++++-------------- infra/docker/compose.yml | 22 +++-- 7 files changed, 174 insertions(+), 138 deletions(-) diff --git a/.env.staging b/.env.staging index d58e723c1..3775ad9c5 100644 --- a/.env.staging +++ b/.env.staging @@ -22,7 +22,7 @@ WEB_URL=http://47.98.113.167:3001 # ======================= # 数据库配置 # ======================= -DATABASE_URL=postgresql+psycopg://postgres:postgres@postgres:5432/xiaoxia_saas_staging +DATABASE_URL=postgresql+psycopg://postgres:CHANGE_ME_STAGING_DB_PASSWORD@postgres:5432/xiaoxia_saas_staging DATABASE_POOL_SIZE=20 DATABASE_MAX_OVERFLOW=40 DATABASE_POOL_TIMEOUT=30 @@ -74,7 +74,7 @@ ALLOWED_FILE_TYPES=video/mp4,video/quicktime,video/x-msvideo,audio/mpeg,audio/wa # ======================= # 安全配置 # ======================= -SECRET_KEY=staging-secret-key-not-for-production +SECRET_KEY=CHANGE_ME_STAGING_SECRET_KEY ACCESS_TOKEN_EXPIRE_MINUTES=60 REFRESH_TOKEN_EXPIRE_DAYS=7 diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index ccf8c8137..2b44b936f 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -52,26 +52,27 @@ jobs: # Load Docker images docker load < /tmp/api-image.tar.gz docker load < /tmp/worker-image.tar.gz - - # Tag as staging - docker tag xiaoxia-saas-api:${{ github.sha }} xiaoxia-saas-api:staging - docker tag xiaoxia-saas-worker:${{ github.sha }} xiaoxia-saas-worker:staging - - # Deploy to staging directory - cd /var/lib/xiaoxia-saas-staging || mkdir -p /var/lib/xiaoxia-saas-staging - - # Update docker-compose - cat > docker-compose.yml << 'COMPOSE' + + # Prepare staging directory + mkdir -p /var/lib/xiaoxia-saas-staging + cp .env.staging /var/lib/xiaoxia-saas-staging/.env + + # Tag as staging + docker tag xiaoxia-saas-api:${{ github.sha }} xiaoxia-saas-api:staging + docker tag xiaoxia-saas-worker:${{ github.sha }} xiaoxia-saas-worker:staging + + # Deploy with staging env + cd /var/lib/xiaoxia-saas-staging + cat > docker-compose.yml << 'COMPOSE' version: '3.9' services: api: image: xiaoxia-saas-api:staging restart: unless-stopped + env_file: + - ./.env ports: - "8001:8000" - environment: - - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/xiaoxia_saas_staging - - REDIS_URL=redis://redis:6379/1 depends_on: - postgres - redis @@ -79,9 +80,8 @@ services: worker: image: xiaoxia-saas-worker:staging restart: unless-stopped - environment: - - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/xiaoxia_saas_staging - - REDIS_URL=redis://redis:6379/1 + env_file: + - ./.env depends_on: - redis - postgres @@ -92,7 +92,7 @@ services: environment: POSTGRES_DB: xiaoxia_saas_staging POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres + POSTGRES_PASSWORD: CHANGE_ME_STAGING_DB_PASSWORD volumes: - postgres_staging_data:/var/lib/postgresql/data @@ -103,12 +103,9 @@ services: volumes: postgres_staging_data: COMPOSE - - # Start services - docker-compose up -d - - # Cleanup - rm -f /tmp/api-image.tar.gz /tmp/worker-image.tar.gz + + docker-compose up -d + rm -f /tmp/api-image.tar.gz /tmp/worker-image.tar.gz echo "✅ Staging deployment completed" @@ -126,33 +123,29 @@ COMPOSE # Load Docker images docker load < /tmp/api-image.tar.gz docker load < /tmp/worker-image.tar.gz - - # Tag as production version - docker tag xiaoxia-saas-api:${{ github.sha }} xiaoxia-saas-api:${{ github.ref_name }} - docker tag xiaoxia-saas-worker:${{ github.sha }} xiaoxia-saas-worker:${{ github.ref_name }} - docker tag xiaoxia-saas-api:${{ github.sha }} xiaoxia-saas-api:latest - docker tag xiaoxia-saas-worker:${{ github.sha }} xiaoxia-saas-worker:latest - - # Deploy to production directory - cd /var/lib/xiaoxia-saas-production || mkdir -p /var/lib/xiaoxia-saas-production - - # Backup current version - if [ -f docker-compose.yml ]; then - cp docker-compose.yml docker-compose.yml.backup - fi - - # Update docker-compose - cat > docker-compose.yml << 'COMPOSE' + + # Prepare production directory + mkdir -p /var/lib/xiaoxia-saas-production + cp .env.production /var/lib/xiaoxia-saas-production/.env + + # Tag as production version + docker tag xiaoxia-saas-api:${{ github.sha }} xiaoxia-saas-api:${{ github.ref_name }} + docker tag xiaoxia-saas-worker:${{ github.sha }} xiaoxia-saas-worker:${{ github.ref_name }} + docker tag xiaoxia-saas-api:${{ github.sha }} xiaoxia-saas-api:latest + docker tag xiaoxia-saas-worker:${{ github.sha }} xiaoxia-saas-worker:latest + + # Deploy with production env + cd /var/lib/xiaoxia-saas-production + cat > docker-compose.yml << 'COMPOSE' version: '3.9' services: api: image: xiaoxia-saas-api:latest restart: unless-stopped + env_file: + - ./.env ports: - "8000:8000" - environment: - - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/xiaoxia_saas_production - - REDIS_URL=redis://redis:6379/0 depends_on: - postgres - redis @@ -160,9 +153,8 @@ services: worker: image: xiaoxia-saas-worker:latest restart: unless-stopped - environment: - - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/xiaoxia_saas_production - - REDIS_URL=redis://redis:6379/0 + env_file: + - ./.env depends_on: - redis - postgres @@ -173,7 +165,7 @@ services: environment: POSTGRES_DB: xiaoxia_saas_production POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres + POSTGRES_PASSWORD: CHANGE_ME_PRODUCTION_DB_PASSWORD volumes: - postgres_production_data:/var/lib/postgresql/data @@ -184,11 +176,8 @@ services: volumes: postgres_production_data: COMPOSE - - # Start services - docker-compose up -d - - # Cleanup - rm -f /tmp/api-image.tar.gz /tmp/worker-image.tar.gz + + docker-compose up -d + rm -f /tmp/api-image.tar.gz /tmp/worker-image.tar.gz echo "✅ Production deployment completed: ${{ github.ref_name }}" diff --git a/apps/api/app/core/database.py b/apps/api/app/core/database.py index 84bf25731..5f6422aa2 100644 --- a/apps/api/app/core/database.py +++ b/apps/api/app/core/database.py @@ -1,9 +1,33 @@ -from pydantic import BaseModel +from pydantic_settings import BaseSettings, SettingsConfigDict +from typing import Optional +import os -class DatabaseSettings(BaseModel): - database_url: str = "postgresql://postgres:postgres@postgres:5432/xiaoxia_saas" +class DatabaseSettings(BaseSettings): + database_url: str = "postgresql+psycopg://postgres:postgres@postgres:5432/xiaoxia_saas" + pool_size: int = 20 + max_overflow: int = 40 + pool_timeout: int = 30 + pool_recycle: int = 3600 + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + case_sensitive=False, + extra="ignore", + ) + + +_settings: Optional[DatabaseSettings] = None def get_database_settings() -> DatabaseSettings: - return DatabaseSettings() + global _settings + if _settings is None: + env = os.getenv("APP_ENV", "development") + env_file = f".env.{env}" if env != "development" else ".env" + if os.path.exists(env_file): + _settings = DatabaseSettings(_env_file=env_file) + else: + _settings = DatabaseSettings() + return _settings diff --git a/apps/api/app/db.py b/apps/api/app/db.py index 4e0d843da..32f6cb3d7 100644 --- a/apps/api/app/db.py +++ b/apps/api/app/db.py @@ -4,7 +4,13 @@ from sqlalchemy.orm import sessionmaker, Session from app.core.database import get_database_settings settings = get_database_settings() -engine = create_engine(settings.database_url) +engine = create_engine( + settings.database_url, + pool_size=settings.pool_size, + max_overflow=settings.max_overflow, + pool_timeout=settings.pool_timeout, + pool_recycle=settings.pool_recycle, +) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/apps/worker/worker_app/core/config.py b/apps/worker/worker_app/core/config.py index 789703fff..9b395ae1d 100644 --- a/apps/worker/worker_app/core/config.py +++ b/apps/worker/worker_app/core/config.py @@ -1,11 +1,33 @@ -from pydantic import BaseModel +from pydantic_settings import BaseSettings, SettingsConfigDict +from typing import Optional +import os -class WorkerSettings(BaseModel): +class WorkerSettings(BaseSettings): worker_name: str = "xiaoxia-saas-worker" broker_url: str = "redis://redis:6379/0" result_backend: str = "redis://redis:6379/1" + worker_concurrency: int = 4 + worker_max_tasks_per_child: int = 1000 + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + case_sensitive=False, + extra="ignore", + ) + + +_settings: Optional[WorkerSettings] = None def get_settings() -> WorkerSettings: - return WorkerSettings() + global _settings + if _settings is None: + env = os.getenv("APP_ENV", "development") + env_file = f".env.{env}" if env != "development" else ".env" + if os.path.exists(env_file): + _settings = WorkerSettings(_env_file=env_file) + else: + _settings = WorkerSettings() + return _settings diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 12f0a1ac0..fccfcd414 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -1,89 +1,80 @@ -# 部署配置说明 +# 部署说明 -## 部署流程 +## 目标 -### Staging(测试环境) -- **触发条件**:push 到 main 分支 -- **部署地址**:http://47.98.113.167:8001 -- **数据库**:xiaoxia_saas_staging +统一采用环境文件驱动部署: -### Production(生产环境) -- **触发条件**:创建 tag (v*) -- **部署地址**:http://47.98.113.167:8000 -- **数据库**:xiaoxia_saas_production +- `infra/docker/compose.yml` 作为本地/开发 compose +- `.env.staging` 作为测试环境配置 +- `.env.production` 作为生产环境配置 -## 部署步骤 +--- -1. **构建 Docker 镜像** - - API 镜像:xiaoxia-saas-api - - Worker 镜像:xiaoxia-saas-worker +## 环境加载规则 -2. **传输镜像到服务器** - - 通过 SSH 上传到 /tmp/ +### 开发环境 +- 默认读取 `.env` +- 没有 `.env` 时回退到代码默认值 -3. **在服务器上部署** - - Staging: /var/lib/xiaoxia-saas-staging - - Production: /var/lib/xiaoxia-saas-production +### Staging +- 读取 `.env.staging` +- `APP_ENV=staging` +- 端口和数据库与生产隔离 -4. **启动服务** - - docker-compose up -d +### Production +- 读取 `.env.production` +- `APP_ENV=production` +- 必须替换 `CHANGE_ME` 项 -## SSH 密钥配置(必须) +--- -### 方案 A:使用现有 SSH 密钥(推荐) +## Docker Compose 约定 -当前 act_runner 运行在服务器本机,可以直接访问本地文件系统。 +### 开发/本地 +```bash +cd infra/docker +docker-compose up -d +``` -1. **配置 runner 使用本地部署** +### Staging +```bash +cp .env.staging .env +cd infra/docker +docker-compose up -d +``` -修改 deploy.yml,将 SSH 命令改为本地命令: -- 不使用 `ssh root@47.98.113.167` -- 直接执行本地命令 +### Production +```bash +cp .env.production .env +cd infra/docker +docker-compose up -d +``` -2. **或者配置 SSH localhost** +--- + +## Gitea Actions 部署建议 + +部署时建议先放置环境文件,再启动服务: ```bash -# 在服务器上配置 SSH localhost 免密登录 -ssh-keygen -t ed25519 -f ~/.ssh/id_deploy -N "" -cat ~/.ssh/id_deploy.pub >> ~/.ssh/authorized_keys -chmod 600 ~/.ssh/authorized_keys +cp .env.staging /var/lib/xiaoxia-saas-staging/.env +cd /var/lib/xiaoxia-saas-staging +docker-compose up -d ``` -### 方案 B:使用 Gitea Secrets +生产环境类似: -如果需要远程部署到其他服务器: - -1. 在 Gitea 仓库设置中添加 Secrets: - - `DEPLOY_SSH_KEY` - SSH 私钥 - - `DEPLOY_HOST` - 部署服务器地址 - - `DEPLOY_USER` - SSH 用户名 - -2. 修改 deploy.yml 使用 secrets: -```yaml -- name: Setup SSH - run: | - mkdir -p ~/.ssh - echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_deploy - chmod 600 ~/.ssh/id_deploy +```bash +cp .env.production /var/lib/xiaoxia-saas-production/.env +cd /var/lib/xiaoxia-saas-production +docker-compose up -d ``` -## 当前状态 +--- -✅ 部署脚本已完成 -⚠️ SSH 配置待完成 -⚠️ 需要选择方案 A 或 B +## 注意事项 -## 推荐方案 - -**方案 A(本地部署)** - 最简单,因为 runner 和服务在同一台机器上。 - -修改 deploy.yml: -- 去掉 `scp` 和 `ssh` 命令 -- 直接在本地执行 docker 命令 -- 路径直接使用本地路径 - -## 下一步 - -1. 选择部署方案(A 或 B) -2. 完成 SSH 配置 -3. 推送代码测试部署 +- `.env.production` 不要把真实密钥提交进仓库 +- `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` 要在部署时注入 +- 数据库密码要通过密钥管理或服务器环境变量替换 +- 生产环境 `DEBUG=false` diff --git a/infra/docker/compose.yml b/infra/docker/compose.yml index b26001f64..802353bd8 100644 --- a/infra/docker/compose.yml +++ b/infra/docker/compose.yml @@ -5,7 +5,7 @@ services: working_dir: /app/apps/web command: sh -c "npm install && npm run dev" ports: - - "3000:3000" + - "${WEB_PORT:-3000}:3000" volumes: - ../..:/app depends_on: @@ -17,9 +17,11 @@ services: context: ../.. dockerfile: infra/docker/api.Dockerfile env_file: - - ../../.env.example + - ../../.env + environment: + APP_ENV: ${APP_ENV:-development} ports: - - "8000:8000" + - "${API_PORT:-8000}:8000" depends_on: - postgres - redis @@ -30,7 +32,9 @@ services: context: ../.. dockerfile: infra/docker/worker.Dockerfile env_file: - - ../../.env.example + - ../../.env + environment: + APP_ENV: ${APP_ENV:-development} depends_on: - redis - postgres @@ -38,18 +42,18 @@ services: postgres: image: postgres:16 environment: - POSTGRES_DB: xiaoxia_saas - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres + POSTGRES_DB: ${POSTGRES_DB:-xiaoxia_saas} + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} ports: - - "5432:5432" + - "${POSTGRES_PORT:-5432}:5432" volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7 ports: - - "6379:6379" + - "${REDIS_PORT:-6379}:6379" volumes: postgres_data: