ci: add GitHub Actions CI/CD pipeline

- Add complete CI/CD workflow with test, build, and deploy stages
- Add Dockerfile with health check and non-root user
- Add comprehensive documentation and setup guides
- Add automated scripts for secrets setup and local verification
- Configure Harbor image registry integration
- Configure automatic deployment to ljwx-deploy repository

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-02-27 00:09:40 +08:00
co-authored by Claude Sonnet 4.6
parent c6a61991d6
commit e5526ce86e
11 changed files with 3037 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
# GitHub Actions CI/CD 文档索引
## 📚 文档列表
### 🚀 快速开始
- **[QUICKSTART.md](QUICKSTART.md)** - 快速参考指南
- 本地验证
- 触发 CI/CD
- 常用命令
- 故障排查
### 🔧 设置指南
- **[SECRETS_SETUP_GUIDE.md](SECRETS_SETUP_GUIDE.md)** - Secrets 设置详细指南
- 图文步骤
- 交互式脚本
- 验证方法
- 常见问题
- **[SETUP.md](SETUP.md)** - 完整设置指南
- 前置条件
- Secrets 配置
- 验证步骤
### 📖 架构文档
- **[README.md](README.md)** - CI/CD 架构文档
- K8s 集群架构
- CI/CD 完整流程
- ljwx-deploy 仓库结构
- Deployment 配置
- 监控和可观测性
### 🛠️ 脚本工具
- **[../../scripts/setup-github-secrets.sh](../../scripts/setup-github-secrets.sh)** - Secrets 设置脚本
- 交互式设置
- 自动验证
- 测试连接
- **[../../scripts/verify-ci.sh](../../scripts/verify-ci.sh)** - 本地 CI 验证脚本
- Linter 检查
- 运行测试
- Docker 构建
- 镜像测试
---
## 🎯 使用流程
### 首次设置
```bash
# 1. 设置 GitHub Secrets
./scripts/setup-github-secrets.sh
# 2. 本地验证
./scripts/verify-ci.sh
# 3. 提交代码
git add .
git commit -m "ci: add CI/CD pipeline"
git push origin main
# 4. 查看运行状态
gh run watch
```
### 日常开发
```bash
# 1. 本地验证
./scripts/verify-ci.sh --skip-docker
# 2. 提交代码
git add .
git commit -m "feat: add new feature"
git push origin main
# 3. 查看运行状态
gh run list
```
---
## 📋 检查清单
### 首次设置
- [ ] 阅读 [SECRETS_SETUP_GUIDE.md](SECRETS_SETUP_GUIDE.md)
- [ ] 运行 `./scripts/setup-github-secrets.sh`
- [ ] 验证 secrets: `gh secret list`
- [ ] 运行 `./scripts/verify-ci.sh`
- [ ] 推送代码触发 CI/CD
- [ ] 检查 GitHub Actions 运行
- [ ] 验证 Harbor 镜像推送
- [ ] 检查 ljwx-deploy PR
### 日常开发
- [ ] 本地运行测试: `cd backend && pytest -v`
- [ ] 本地运行 linter: `cd backend && ruff check .`
- [ ] 运行本地验证: `./scripts/verify-ci.sh`
- [ ] 提交代码
- [ ] 检查 GitHub Actions
- [ ] 合并 PR 后验证部署
---
## 🔗 相关链接
### GitHub
- **仓库**: https://github.com/BrunoGao/ljwx-shiti
- **Actions**: https://github.com/BrunoGao/ljwx-shiti/actions
- **Settings**: https://github.com/BrunoGao/ljwx-shiti/settings
### 部署
- **ljwx-deploy**: https://github.com/BrunoGao/ljwx-deploy
- **Harbor**: https://harbor.omniverseai.net
- **ArgoCD**: http://argocd.omniverseai.net
### 文档
- **项目文档**: [../../docs/](../../docs/)
- **API 文档**: [../../docs/API.md](../../docs/API.md)
- **架构文档**: [../../docs/architecture.md](../../docs/architecture.md)
---
**最后更新**: 2026-02-26
+219
View File
@@ -0,0 +1,219 @@
# CI/CD 快速参考
## 🚀 快速开始
### 1. 本地验证(推荐先执行)
```bash
# 完整验证(包括测试和 Docker 构建)
./scripts/verify-ci.sh
# 只验证 Docker 构建
./scripts/verify-ci.sh --skip-tests
# 只运行测试
./scripts/verify-ci.sh --skip-docker
```
### 2. 配置 GitHub Secrets
访问: https://github.com/BrunoGao/ljwx-shiti/settings/secrets/actions
添加以下 Secrets:
- `HARBOR_USERNAME`: Harbor 用户名
- `HARBOR_PASSWORD`: Harbor 密码
- `DEPLOY_REPO_TOKEN`: GitHub Personal Access Token
详细步骤见: [SETUP.md](.github/workflows/SETUP.md)
### 3. 触发 CI/CD
```bash
# 提交代码
git add .
git commit -m "feat: add new feature"
git push origin main
# 查看运行状态
open https://github.com/BrunoGao/ljwx-shiti/actions
```
---
## 📋 Workflow 流程
```
Push to main/master
↓
┌─────────────────┐
│ 1. Test │ 运行测试、linter、coverage
│ (20 min) │ ✅ 通过才继续
└────────┬────────┘
↓
┌─────────────────┐
│ 2. Build │ 构建 Docker 镜像
│ (30 min) │ 推送到 Harbor
└────────┬────────┘
↓
┌─────────────────┐
│ 3. Deploy │ 更新 ljwx-deploy
│ (10 min) │ 创建 PR
└────────┬────────┘
↓
ArgoCD 自动部署
```
---
## 🔧 常用命令
### 本地开发
```bash
# 启动本地环境
docker-compose up -d
# 运行测试
cd backend && pytest -v
# 运行 linter
cd backend && ruff check .
# 格式化代码
cd backend && ruff format .
```
### Docker 操作
```bash
# 构建镜像
docker build -t ljwx-shiti:dev -f backend/Dockerfile .
# 运行容器
docker run -p 8000:8000 ljwx-shiti:dev
# 查看日志
docker logs -f <container-id>
# 进入容器
docker exec -it <container-id> /bin/bash
```
### GitHub Actions
```bash
# 查看 workflow 状态
gh run list --workflow=ci.yml
# 查看最新运行的日志
gh run view --log
# 手动触发 workflow
gh workflow run ci.yml
```
---
## 🐛 故障排查
### 测试失败
```bash
# 本地运行测试
cd backend
pytest -v --tb=short
# 查看覆盖率
pytest --cov=app --cov-report=html
open htmlcov/index.html
```
### Docker 构建失败
```bash
# 查看构建日志
docker build -t ljwx-shiti:debug -f backend/Dockerfile . --progress=plain
# 检查依赖
cd backend
pip install -e ".[dev]"
```
### Harbor 推送失败
```bash
# 测试 Harbor 登录
docker login harbor.omniverseai.net
# 手动推送测试
docker tag ljwx-shiti:dev harbor.omniverseai.net/ljwx/ljwx-shiti:test
docker push harbor.omniverseai.net/ljwx/ljwx-shiti:test
```
### Deploy PR 创建失败
```bash
# 检查 token 权限
curl -H "Authorization: token YOUR_TOKEN" \
https://api.github.com/repos/BrunoGao/ljwx-deploy
# 手动克隆测试
git clone https://x-access-token:YOUR_TOKEN@github.com/BrunoGao/ljwx-deploy.git
```
---
## 📊 监控
### GitHub Actions
- **Actions 页面**: https://github.com/BrunoGao/ljwx-shiti/actions
- **Workflow 文件**: `.github/workflows/ci.yml`
- **Status Badge**:
```markdown
[![CI/CD](https://github.com/BrunoGao/ljwx-shiti/actions/workflows/ci.yml/badge.svg)](https://github.com/BrunoGao/ljwx-shiti/actions/workflows/ci.yml)
```
### Harbor
- **Harbor UI**: https://harbor.omniverseai.net
- **项目**: ljwx
- **镜像**: ljwx-shiti
### ArgoCD
- **ArgoCD UI**: http://argocd.omniverseai.net
- **Application**: ljwx-shiti-dev / ljwx-shiti-prod
---
## 📚 文档
- **架构文档**: [.github/workflows/README.md](.github/workflows/README.md)
- **设置指南**: [.github/workflows/SETUP.md](.github/workflows/SETUP.md)
- **项目文档**: [docs/](../docs/)
---
## 🎯 检查清单
### 首次设置
- [ ] 配置 GitHub Secrets (HARBOR_USERNAME, HARBOR_PASSWORD, DEPLOY_REPO_TOKEN)
- [ ] 运行本地验证 (`./scripts/verify-ci.sh`)
- [ ] 推送代码触发 CI/CD
- [ ] 验证 GitHub Actions 运行成功
- [ ] 检查 Harbor 镜像推送
- [ ] 验证 ljwx-deploy PR 创建
### 日常开发
- [ ] 本地运行测试通过
- [ ] 本地运行 linter 通过
- [ ] 提交代码前运行 `./scripts/verify-ci.sh`
- [ ] 推送代码后检查 GitHub Actions
- [ ] 合并 PR 后验证部署
---
**最后更新**: 2026-02-26
+712
View File
@@ -0,0 +1,712 @@
# GitHub Actions CI/CD 架构文档
## 概览
本文档描述了 ljwx-shiti (智能错题本) 项目的 CI/CD 流程和 K8s 部署架构。
**关键组件**:
- **代码仓库**: GitHub (git@github.com:BrunoGao/ljwx-shiti.git)
- **部署仓库**: GitHub (git@github.com:BrunoGao/ljwx-deploy.git)
- **镜像仓库**: Harbor (https://harbor.omniverseai.net)
- **K8s 集群**: 本地 K8s (基础设施在 infra namespace)
- **GitOps 工具**: ArgoCD
---
## 1. K8s 集群架构
### 1.1 Infra Namespace 基础设施
所有共享的基础设施服务都部署在 `infra` namespace 中,应用通过跨 namespace 服务发现访问。
#### PostgreSQL 服务
```yaml
# 内部访问 (应用使用)
postgres-infra-postgresql.infra.svc.cluster.local:5432
ClusterIP: 192.168.194.246
# Headless Service (StatefulSet 使用)
postgres-infra-postgresql-hl.infra.svc.cluster.local
# 外部访问 (开发调试)
postgres-external (NodePort: 32432)
# 部署方式
StatefulSet: postgres-infra-postgresql-0 (1 副本)
```
#### Redis 服务
```yaml
# 主节点访问 (应用使用)
redis-infra-master.infra.svc.cluster.local:6379
ClusterIP: 192.168.194.187
# Headless Service
redis-infra-headless.infra.svc.cluster.local
# 外部访问 (开发调试)
redis-external (NodePort: 32379)
# 部署方式
StatefulSet: redis-infra-master-0 (1 副本)
```
#### MySQL 服务
```yaml
# 内部访问 (应用使用)
mysql-infra.infra.svc.cluster.local:3306
Headless Service
# 外部访问 (开发调试)
mysql-external (NodePort: 32306)
# 部署方式
StatefulSet: mysql-infra-0 (1 副本)
```
#### 其他基础设施
- **MinIO**: 对象存储 (S3 兼容)
- **ClickHouse**: 分析数据库
- **Redpanda**: Kafka 兼容消息队列
- **Ingress Nginx**: 入口控制器
### 1.2 跨 Namespace 服务访问
**DNS 格式**: `<service-name>.<namespace>.svc.cluster.local`
**应用配置示例**:
```yaml
# Deployment 环境变量
env:
- name: DATABASE_URL
value: postgresql+asyncpg://user:pass@postgres-infra-postgresql.infra.svc.cluster.local:5432/wrongbook
- name: REDIS_URL
value: redis://redis-infra-master.infra.svc.cluster.local:6379/0
```
**InitContainer 等待依赖**:
```yaml
initContainers:
- name: wait-for-postgres
image: busybox:1.35
command:
- sh
- -c
- |
until nc -z postgres-infra-postgresql.infra.svc.cluster.local 5432; do
echo "Waiting for PostgreSQL..."
sleep 2
done
```
---
## 2. CI/CD 完整流程
### 2.1 流程图
```
┌─────────────────────────────────────────────────────────────┐
│ 1. 开发者推送代码 │
│ git push origin main │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. GitHub Actions 触发 │
│ Workflow: .github/workflows/ci.yml │
│ - Lint & Test (pytest, ruff) │
│ - Build Docker Image │
│ - Push to Harbor │
│ Image: harbor.omniverseai.net/ljwx/ljwx-shiti:${SHA} │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. 自动更新部署仓库 │
│ - Clone ljwx-deploy │
│ - Update kustomization.yaml │
│ Path: apps/ljwx-shiti/overlays/prod/kustomization.yaml│
│ - Create PR to main branch │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. PR 合并到 ljwx-deploy/main │
│ (手动审核或自动合并) │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. ArgoCD 自动同步 (30秒内) │
│ - 检测 Git 变更 │
│ - 应用 Kustomize 配置 │
│ - 滚动更新 Deployment │
│ - 健康检查 (liveness/readiness/startup probes) │
└────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. 应用运行在 K8s 集群 │
│ - 连接 infra namespace 的 PostgreSQL/Redis │
│ - 通过 Ingress 暴露服务 │
│ - Prometheus 监控 (ServiceMonitor) │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 GitHub Actions Workflow 配置
#### 必需的 GitHub Secrets
在 GitHub 仓库设置中配置以下 Secrets:
```bash
# Harbor 镜像仓库凭据
HARBOR_USERNAME=admin
HARBOR_PASSWORD=<harbor-password>
# 部署仓库访问 Token
DEPLOY_REPO_TOKEN=<github-personal-access-token>
# Token 权限: repo, write:repository
```
#### Workflow 文件结构
```yaml
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
env:
HARBOR_HOST: harbor.omniverseai.net
HARBOR_PROJECT: ljwx
IMAGE_NAME: ljwx-shiti
jobs:
test:
# 运行测试
build-and-push:
# 构建并推送镜像
update-deploy:
# 更新部署配置
```
### 2.3 镜像构建策略
#### Backend (Python FastAPI)
```dockerfile
# backend/Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# 安装 Python 依赖
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY backend/ .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```
#### 构建命令
```bash
# 单架构构建
docker build -t harbor.omniverseai.net/ljwx/ljwx-shiti:${GITHUB_SHA} \
-f backend/Dockerfile .
docker push harbor.omniverseai.net/ljwx/ljwx-shiti:${GITHUB_SHA}
# 多架构构建 (可选)
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
-t harbor.omniverseai.net/ljwx/ljwx-shiti:${GITHUB_SHA} \
-f backend/Dockerfile .
```
---
## 3. ljwx-deploy 仓库结构
### 3.1 目录结构
```
ljwx-deploy/
├── apps/
│ └── ljwx-shiti/
│ ├── base/ # 基础配置
│ │ ├── namespace.yaml # Namespace 定义
│ │ ├── configmap.yaml # 非敏感配置
│ │ ├── deployment.yaml # Deployment 配置
│ │ ├── service.yaml # Service 配置
│ │ ├── ingress.yaml # Ingress 配置
│ │ ├── servicemonitor.yaml # Prometheus 监控
│ │ └── kustomization.yaml # Kustomize 配置
│ └── overlays/
│ ├── dev/ # 开发环境
│ │ └── kustomization.yaml
│ ├── staging/ # 预发布环境
│ │ └── kustomization.yaml
│ └── prod/ # 生产环境
│ └── kustomization.yaml
├── argocd-apps/
│ ├── 80-ljwx-shiti-dev.yaml # ArgoCD Application (dev)
│ ├── 81-ljwx-shiti-staging.yaml # ArgoCD Application (staging)
│ └── 82-ljwx-shiti-prod.yaml # ArgoCD Application (prod)
└── cluster/
└── namespace-ljwx-shiti.yaml # 集群级别 Namespace 配置
```
### 3.2 Kustomize 配置
#### base/kustomization.yaml
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-shiti
resources:
- namespace.yaml
- configmap.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
- servicemonitor.yaml
commonLabels:
app: ljwx-shiti
managed-by: kustomize
# 镜像配置 (由 CI 自动更新)
images:
- name: harbor.omniverseai.net/ljwx/ljwx-shiti
newTag: latest # CI 会更新为具体的 commit SHA
generatorOptions:
disableNameSuffixHash: true
```
#### overlays/prod/kustomization.yaml
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-shiti
resources:
- ../../base
# 生产环境特定配置
patchesStrategicMerge:
- |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-shiti
spec:
replicas: 3 # 生产环境 3 副本
template:
spec:
containers:
- name: ljwx-shiti
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 2Gi
# 镜像标签 (由 CI 自动更新)
images:
- name: harbor.omniverseai.net/ljwx/ljwx-shiti
newTag: abc123def # CI 更新为最新的 commit SHA
```
### 3.3 Deployment 配置示例
```yaml
# apps/ljwx-shiti/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-shiti
namespace: ljwx-shiti
labels:
app: ljwx-shiti
component: backend
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: ljwx-shiti
template:
metadata:
labels:
app: ljwx-shiti
component: backend
spec:
# 等待 PostgreSQL 就绪
initContainers:
- name: wait-for-postgres
image: busybox:1.35
command:
- sh
- -c
- |
until nc -z postgres-infra-postgresql.infra.svc.cluster.local 5432; do
echo "Waiting for PostgreSQL..."
sleep 2
done
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
containers:
- name: ljwx-shiti
image: harbor.omniverseai.net/ljwx/ljwx-shiti:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 8000
protocol: TCP
# 环境变量 - 从 ConfigMap
envFrom:
- configMapRef:
name: ljwx-shiti-config
# 环境变量 - 从 Secret (敏感信息)
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: ljwx-shiti-secret
key: DATABASE_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: ljwx-shiti-secret
key: REDIS_URL
- name: ANTHROPIC_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: ljwx-shiti-secret
key: ANTHROPIC_AUTH_TOKEN
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: ljwx-shiti-secret
key: JWT_SECRET
# 资源配置
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
# 健康检查
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
restartPolicy: Always
terminationGracePeriodSeconds: 60
```
### 3.4 ArgoCD Application 配置
```yaml
# argocd-apps/80-ljwx-shiti-dev.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ljwx-shiti-dev
namespace: argocd
spec:
project: default
source:
repoURL: git@github.com:BrunoGaoSZ/ljwx-deploy.git
targetRevision: main
path: apps/ljwx-shiti/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: ljwx-shiti
syncPolicy:
automated:
prune: true # 自动删除不在 Git 中的资源
selfHeal: true # 自动修复手动修改
syncOptions:
- CreateNamespace=true
```
---
## 4. Secret 管理
### 4.1 创建 Secret
```bash
# 在 K8s 集群中创建 Secret (不提交到 Git)
kubectl create secret generic ljwx-shiti-secret \
--namespace=ljwx-shiti \
--from-literal=DATABASE_URL='postgresql+asyncpg://wrongbook:password@postgres-infra-postgresql.infra.svc.cluster.local:5432/wrongbook' \
--from-literal=REDIS_URL='redis://redis-infra-master.infra.svc.cluster.local:6379/0' \
--from-literal=ANTHROPIC_AUTH_TOKEN='sk-ant-xxx' \
--from-literal=JWT_SECRET='your-secret-key' \
--from-literal=MINIO_ENDPOINT='minio.infra.svc.cluster.local:9000' \
--from-literal=MINIO_ACCESS_KEY='minioadmin' \
--from-literal=MINIO_SECRET_KEY='minioadmin'
```
### 4.2 Secret 最佳实践
- ❌ **不要**: 将 Secret 提交到 Git 仓库
- ✅ **推荐**: 使用 External Secrets Operator (未来迁移)
- ✅ **推荐**: 使用 Sealed Secrets
- ✅ **当前**: 手动在集群中创建 Secret
---
## 5. 监控和可观测性
### 5.1 Prometheus 监控
```yaml
# apps/ljwx-shiti/base/servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ljwx-shiti
namespace: ljwx-shiti
spec:
selector:
matchLabels:
app: ljwx-shiti
endpoints:
- port: http
path: /metrics
interval: 30s
```
### 5.2 日志收集
```yaml
# Deployment annotations
annotations:
fluentbit.io/parser: json
```
### 5.3 追踪 (OpenTelemetry)
```yaml
# ConfigMap 环境变量
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.tracing.svc.cluster.local:4318"
OTEL_SERVICE_NAME: "ljwx-shiti"
```
---
## 6. 常用命令
### 6.1 本地开发
```bash
# 启动本地环境
docker-compose up -d
# 运行测试
cd backend && pytest -v
# 运行 linter
cd backend && ruff check .
# 构建镜像
docker build -t ljwx-shiti:dev -f backend/Dockerfile .
```
### 6.2 K8s 操作
```bash
# 查看应用状态
kubectl get pods -n ljwx-shiti
kubectl get svc -n ljwx-shiti
kubectl get ingress -n ljwx-shiti
# 查看日志
kubectl logs -n ljwx-shiti -l app=ljwx-shiti -f
# 查看 Pod 详情
kubectl describe pod -n ljwx-shiti <pod-name>
# 进入容器
kubectl exec -it -n ljwx-shiti <pod-name> -- /bin/bash
# 查看 Secret
kubectl get secret -n ljwx-shiti ljwx-shiti-secret -o yaml
# 测试数据库连接
kubectl exec -it -n ljwx-shiti <pod-name> -- \
psql postgresql://wrongbook:password@postgres-infra-postgresql.infra.svc.cluster.local:5432/wrongbook
```
### 6.3 ArgoCD 操作
```bash
# 查看 Application 状态
kubectl get application -n argocd ljwx-shiti-dev
# 手动触发同步
kubectl patch application ljwx-shiti-dev -n argocd \
--type merge \
-p '{"operation":{"initiatedBy":{"username":"admin"},"sync":{"revision":"HEAD"}}}'
# 查看同步历史
kubectl describe application -n argocd ljwx-shiti-dev
```
### 6.4 Harbor 操作
```bash
# 登录 Harbor
docker login harbor.omniverseai.net
# 查看镜像
docker pull harbor.omniverseai.net/ljwx/ljwx-shiti:latest
# 查看镜像详情
docker buildx imagetools inspect harbor.omniverseai.net/ljwx/ljwx-shiti:latest
```
---
## 7. 故障排查
### 7.1 Pod 无法启动
```bash
# 查看 Pod 事件
kubectl describe pod -n ljwx-shiti <pod-name>
# 查看 init container 日志
kubectl logs -n ljwx-shiti <pod-name> -c wait-for-postgres
# 测试数据库连接
kubectl exec -n ljwx-shiti <pod-name> -- \
nc -zv postgres-infra-postgresql.infra.svc.cluster.local 5432
```
### 7.2 镜像拉取失败
```bash
# 检查 imagePullSecrets
kubectl get secret -n ljwx-shiti regcred
# 手动拉取镜像测试
docker pull harbor.omniverseai.net/ljwx/ljwx-shiti:latest
```
### 7.3 健康检查失败
```bash
# 查看健康检查端点
kubectl exec -n ljwx-shiti <pod-name> -- \
curl -v http://localhost:8000/health
# 查看应用日志
kubectl logs -n ljwx-shiti <pod-name> --tail=100
```
### 7.4 ArgoCD 同步失败
```bash
# 查看 Application 状态
kubectl get application -n argocd ljwx-shiti-dev -o yaml
# 查看 ArgoCD 日志
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller
```
---
## 8. 参考资料
### 8.1 相关仓库
- **应用仓库**: git@github.com:BrunoGao/ljwx-shiti.git
- **部署仓库**: git@github.com:BrunoGao/ljwx-deploy.git
- **Harbor**: https://harbor.omniverseai.net/harbor/projects/ljwx
### 8.2 文档
- **项目文档**: `/docs/`
- **API 文档**: `/docs/API.md`
- **架构文档**: `/docs/architecture.md`
- **部署文档**: `/docs/DEPLOYMENT.md`
### 8.3 工具
- **Kustomize**: https://kustomize.io/
- **ArgoCD**: https://argo-cd.readthedocs.io/
- **Harbor**: https://goharbor.io/
---
**最后更新**: 2026-02-26
**维护者**: DevOps Team
+392
View File
@@ -0,0 +1,392 @@
# GitHub Secrets 设置详细指南
本文档提供 GitHub Actions CI/CD 所需 Secrets 的详细设置步骤。
## 📋 前置条件
- GitHub 账号已登录
- 对 `BrunoGao/ljwx-shiti` 仓库有管理员权限
- 已安装 GitHub CLI (`gh`)
## 🚀 快速设置(推荐)
### 方式 1: 使用交互式脚本
```bash
# 运行设置脚本
./scripts/setup-github-secrets.sh
# 按照提示操作:
# 1. 选择 "4. 一键设置所有 secrets"
# 2. 输入 Harbor 用户名和密码
# 3. 输入 GitHub Personal Access Token
# 4. 完成!
```
### 方式 2: 使用 GitHub CLI 命令
```bash
# 设置 Harbor 用户名
echo "admin" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
# 设置 Harbor 密码(会提示输入)
gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
# 设置 Deploy Token(会提示输入)
gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
```
---
## 📝 详细步骤
### 步骤 1: 访问 Secrets 设置页面
#### 方式 A: 通过浏览器
1. 打开仓库: https://github.com/BrunoGao/ljwx-shiti
2. 点击 **Settings** (设置)
3. 在左侧菜单中,点击 **Secrets and variables** → **Actions**
4. 点击 **New repository secret** (新建仓库密钥)
#### 方式 B: 直接访问
访问: https://github.com/BrunoGao/ljwx-shiti/settings/secrets/actions
---
### 步骤 2: 设置 HARBOR_USERNAME
#### 2.1 创建 Secret
- **Name**: `HARBOR_USERNAME`
- **Secret**: `admin` (或你的 Harbor 用户名)
#### 2.2 点击 "Add secret"
![Harbor Username](https://docs.github.com/assets/cb-28937/images/help/repository/actions-secrets-add.png)
#### 2.3 验证
```bash
# 使用 gh CLI 验证
gh secret list --repo BrunoGao/ljwx-shiti | grep HARBOR_USERNAME
```
---
### 步骤 3: 设置 HARBOR_PASSWORD
#### 3.1 获取 Harbor 密码
联系 DevOps 团队或从 Harbor 管理界面获取密码。
#### 3.2 创建 Secret
- **Name**: `HARBOR_PASSWORD`
- **Secret**: `<your-harbor-password>`
#### 3.3 测试 Harbor 登录
```bash
# 测试 Harbor 登录
docker login harbor.omniverseai.net
# 输入用户名: admin
# 输入密码: <your-harbor-password>
# 如果成功,会显示: Login Succeeded
```
---
### 步骤 4: 创建 GitHub Personal Access Token
#### 4.1 访问 Token 创建页面
1. 访问: https://github.com/settings/tokens
2. 点击 **Generate new token** → **Generate new token (classic)**
#### 4.2 配置 Token
**Token 名称**:
```
ljwx-shiti-deploy-access
```
**Token 描述** (可选):
```
CI/CD access to ljwx-deploy repository
```
**过期时间**:
- 推荐: **90 days** (90 天)
- 或选择: **No expiration** (永不过期,需定期审计)
**权限选择** (Scopes):
必需权限:
- ✅ `repo` - Full control of private repositories
- ✅ `repo:status` - Access commit status
- ✅ `repo_deployment` - Access deployment status
- ✅ `public_repo` - Access public repositories
- ✅ `repo:invite` - Access repository invitations
- ✅ `security_events` - Read and write security events
- ✅ `workflow` - Update GitHub Action workflows
可选权限:
- ⬜ `write:packages` - Upload packages to GitHub Package Registry
- ⬜ `read:packages` - Download packages from GitHub Package Registry
#### 4.3 生成 Token
1. 点击 **Generate token** (生成 token)
2. **重要**: 立即复制生成的 token(只显示一次!)
3. Token 格式: `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
#### 4.4 保存 Token
```bash
# 临时保存到环境变量(可选)
export DEPLOY_REPO_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 或保存到密码管理器(推荐)
```
---
### 步骤 5: 设置 DEPLOY_REPO_TOKEN
#### 5.1 创建 Secret
- **Name**: `DEPLOY_REPO_TOKEN`
- **Secret**: `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` (你的 token)
#### 5.2 验证 Token 权限
```bash
# 测试 token 是否有权限访问 ljwx-deploy
curl -H "Authorization: token ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
https://api.github.com/repos/BrunoGao/ljwx-deploy
# 应该返回仓库信息,包含 "name": "ljwx-deploy"
```
---
## ✅ 验证配置
### 方式 1: 使用脚本验证
```bash
./scripts/setup-github-secrets.sh
# 选择 "5. 验证 secrets 配置"
```
### 方式 2: 使用 gh CLI 验证
```bash
# 列出所有 secrets
gh secret list --repo BrunoGao/ljwx-shiti
# 应该看到:
# HARBOR_USERNAME Updated 2026-02-26
# HARBOR_PASSWORD Updated 2026-02-26
# DEPLOY_REPO_TOKEN Updated 2026-02-26
```
### 方式 3: 通过浏览器验证
访问: https://github.com/BrunoGao/ljwx-shiti/settings/secrets/actions
应该看到 3 个 secrets:
- ✅ HARBOR_USERNAME
- ✅ HARBOR_PASSWORD
- ✅ DEPLOY_REPO_TOKEN
---
## 🧪 测试 CI/CD
### 1. 提交代码触发 CI/CD
```bash
# 创建测试分支
git checkout -b test/ci-setup
# 修改文件触发 CI
echo "# CI/CD Test" >> README.md
# 提交并推送
git add README.md
git commit -m "test: verify CI/CD setup"
git push origin test/ci-setup
```
### 2. 查看 GitHub Actions 运行
#### 方式 A: 浏览器
访问: https://github.com/BrunoGao/ljwx-shiti/actions
#### 方式 B: GitHub CLI
```bash
# 查看最近的运行
gh run list --repo BrunoGao/ljwx-shiti
# 实时查看运行状态
gh run watch --repo BrunoGao/ljwx-shiti
# 查看运行日志
gh run view --log --repo BrunoGao/ljwx-shiti
```
### 3. 验证各个阶段
#### ✅ 阶段 1: Test (约 20 分钟)
- Lint 检查
- 单元测试
- 代码覆盖率
#### ✅ 阶段 2: Build and Push (约 30 分钟)
- Docker 镜像构建
- 推送到 Harbor
- 镜像标签: `harbor.omniverseai.net/ljwx/ljwx-shiti:<commit-sha>`
#### ✅ 阶段 3: Update Deploy (约 10 分钟)
- 克隆 ljwx-deploy 仓库
- 更新 kustomization.yaml
- 创建 PR
---
## 🐛 常见问题
### 问题 1: Harbor 登录失败
**错误信息**:
```
Error: Error response from daemon: Get "https://harbor.omniverseai.net/v2/": unauthorized
```
**解决方案**:
1. 检查 `HARBOR_USERNAME` 和 `HARBOR_PASSWORD` 是否正确
2. 在本地测试登录:
```bash
docker login harbor.omniverseai.net
```
3. 确认账号有 `ljwx` 项目的推送权限
### 问题 2: Deploy Repo 访问失败
**错误信息**:
```
remote: Repository not found.
fatal: repository 'https://github.com/BrunoGao/ljwx-deploy.git/' not found
```
**解决方案**:
1. 检查 `DEPLOY_REPO_TOKEN` 是否有效
2. 确认 token 有 `repo` 和 `workflow` 权限
3. 测试 token:
```bash
curl -H "Authorization: token YOUR_TOKEN" \
https://api.github.com/repos/BrunoGao/ljwx-deploy
```
### 问题 3: Secret 无法读取
**错误信息**:
```
Error: Secret HARBOR_USERNAME not found
```
**解决方案**:
1. 确认 secret 名称拼写正确(区分大小写)
2. 检查 secret 是否在正确的仓库中
3. 重新设置 secret:
```bash
gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
```
### 问题 4: Token 权限不足
**错误信息**:
```
Error: Resource not accessible by integration
```
**解决方案**:
1. 检查 token 权限是否包含 `repo` 和 `workflow`
2. 重新生成 token 并确保选择正确的权限
3. 更新 secret:
```bash
gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
```
---
## 🔒 安全最佳实践
### 1. Secret 管理
- ❌ **不要**: 在代码中硬编码 secrets
- ❌ **不要**: 在日志中打印 secrets
- ❌ **不要**: 将 secrets 提交到 Git
- ✅ **推荐**: 使用 GitHub Secrets 存储敏感信息
- ✅ **推荐**: 定期轮换 tokens 和密码(建议 90 天)
### 2. Token 权限最小化
- 只授予必需的权限
- 为不同用途创建不同的 tokens
- 定期审计 token 使用情况
### 3. 审计日志
定期检查:
- GitHub Actions 运行日志
- Harbor 镜像推送记录
- ljwx-deploy 仓库的 commit 历史
### 4. Token 过期处理
当 token 过期时:
1. 生成新的 token
2. 更新 `DEPLOY_REPO_TOKEN` secret
3. 验证 CI/CD 运行正常
---
## 📚 参考资料
### GitHub 文档
- [GitHub Secrets 文档](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
- [Personal Access Tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
- [GitHub Actions 文档](https://docs.github.com/en/actions)
### 项目文档
- [CI/CD 架构文档](.github/workflows/README.md)
- [快速开始指南](.github/workflows/QUICKSTART.md)
- [本地验证脚本](../scripts/verify-ci.sh)
### 工具
- [GitHub CLI](https://cli.github.com/)
- [Harbor 文档](https://goharbor.io/docs/)
---
## 📞 获取帮助
如果遇到问题:
1. 查看 [常见问题](#-常见问题) 部分
2. 运行验证脚本: `./scripts/setup-github-secrets.sh`
3. 查看 GitHub Actions 日志
4. 联系 DevOps 团队
---
**最后更新**: 2026-02-26
**维护者**: DevOps Team
+230
View File
@@ -0,0 +1,230 @@
# GitHub Actions 设置指南
本文档描述如何配置 GitHub Actions CI/CD 流程所需的 Secrets 和权限。
## 1. 必需的 GitHub Secrets
在 GitHub 仓库设置中配置以下 Secrets:
### 1.1 访问 Secrets 设置
1. 打开仓库: https://github.com/BrunoGao/ljwx-shiti
2. 点击 **Settings** → **Secrets and variables** → **Actions**
3. 点击 **New repository secret**
### 1.2 配置 Secrets
#### HARBOR_USERNAME
- **名称**: `HARBOR_USERNAME`
- **值**: Harbor 仓库用户名(通常是 `admin`)
- **用途**: 登录 Harbor 镜像仓库
#### HARBOR_PASSWORD
- **名称**: `HARBOR_PASSWORD`
- **值**: Harbor 仓库密码
- **用途**: 登录 Harbor 镜像仓库
- **获取方式**: 联系 DevOps 团队或从 Harbor 管理界面获取
#### DEPLOY_REPO_TOKEN
- **名称**: `DEPLOY_REPO_TOKEN`
- **值**: GitHub Personal Access Token (PAT)
- **用途**: 访问和更新 ljwx-deploy 仓库
- **权限要求**:
- `repo` (完整仓库访问)
- `workflow` (更新 workflow 文件)
**创建 PAT 步骤**:
1. 访问: https://github.com/settings/tokens
2. 点击 **Generate new token** → **Generate new token (classic)**
3. 设置名称: `ljwx-shiti-deploy-access`
4. 选择权限:
- ✅ `repo` (Full control of private repositories)
- ✅ `workflow` (Update GitHub Action workflows)
5. 点击 **Generate token**
6. 复制生成的 token(只显示一次!)
7. 将 token 添加到 ljwx-shiti 仓库的 Secrets
## 2. 验证 Secrets 配置
配置完成后,可以通过以下方式验证:
### 2.1 检查 Secrets 列表
在 **Settings** → **Secrets and variables** → **Actions** 页面,应该看到:
```
✅ HARBOR_USERNAME
✅ HARBOR_PASSWORD
✅ DEPLOY_REPO_TOKEN
```
### 2.2 触发测试构建
```bash
# 创建测试分支
git checkout -b test/ci-setup
# 修改任意文件触发 CI
echo "# CI Test" >> README.md
# 提交并推送
git add README.md
git commit -m "test: verify CI/CD setup"
git push origin test/ci-setup
```
然后访问: https://github.com/BrunoGao/ljwx-shiti/actions
查看 workflow 运行状态。
## 3. 常见问题排查
### 3.1 Harbor 登录失败
**错误信息**:
```
Error: Error response from daemon: Get "https://harbor.omniverseai.net/v2/": unauthorized
```
**解决方案**:
1. 检查 `HARBOR_USERNAME` 和 `HARBOR_PASSWORD` 是否正确
2. 在本地测试 Harbor 登录:
```bash
docker login harbor.omniverseai.net
# 输入用户名和密码
```
3. 确认 Harbor 账号有 `ljwx` 项目的推送权限
### 3.2 Deploy Repo 访问失败
**错误信息**:
```
remote: Repository not found.
fatal: repository 'https://github.com/BrunoGao/ljwx-deploy.git/' not found
```
**解决方案**:
1. 检查 `DEPLOY_REPO_TOKEN` 是否有效
2. 确认 token 有 `repo` 权限
3. 测试 token 权限:
```bash
curl -H "Authorization: token YOUR_TOKEN" \
https://api.github.com/repos/BrunoGao/ljwx-deploy
```
4. 如果 token 过期,重新生成并更新 Secret
### 3.3 PR 创建失败
**错误信息**:
```
gh: command not found
```
**解决方案**:
这不应该发生,因为 GitHub Actions runner 预装了 `gh` CLI。如果遇到此问题:
1. 检查 workflow 文件中的 `GH_TOKEN` 环境变量设置
2. 确认使用的是 `ubuntu-latest` runner
### 3.4 测试失败
**错误信息**:
```
FAILED tests/test_xxx.py::test_xxx - AssertionError
```
**解决方案**:
1. 在本地运行测试确认通过:
```bash
cd backend
pytest -v
```
2. 检查测试依赖的环境变量是否正确
3. 查看 GitHub Actions 日志获取详细错误信息
## 4. Workflow 触发条件
### 4.1 自动触发
Workflow 会在以下情况自动触发:
- **Push 到 main/master/develop 分支**: 运行完整 CI/CD(测试 + 构建 + 部署)
- **Pull Request 到 main/master/develop**: 只运行测试
### 4.2 手动触发
可以在 GitHub Actions 页面手动触发 workflow:
1. 访问: https://github.com/BrunoGao/ljwx-shiti/actions
2. 选择 **CI/CD Pipeline** workflow
3. 点击 **Run workflow**
4. 选择分支并点击 **Run workflow**
## 5. 监控和通知
### 5.1 查看 Workflow 状态
访问: https://github.com/BrunoGao/ljwx-shiti/actions
可以看到:
- ✅ 成功的 workflow 运行
- ❌ 失败的 workflow 运行
- 🟡 进行中的 workflow 运行
### 5.2 配置通知
在 **Settings** → **Notifications** 中配置:
- Email 通知
- Slack 通知(需要配置 Slack App)
### 5.3 Status Badge
在 README.md 中添加 status badge:
```markdown
[![CI/CD Pipeline](https://github.com/BrunoGao/ljwx-shiti/actions/workflows/ci.yml/badge.svg)](https://github.com/BrunoGao/ljwx-shiti/actions/workflows/ci.yml)
```
## 6. 安全最佳实践
### 6.1 Secrets 管理
- ❌ **不要**: 在代码中硬编码 secrets
- ❌ **不要**: 在日志中打印 secrets
- ✅ **推荐**: 使用 GitHub Secrets 存储敏感信息
- ✅ **推荐**: 定期轮换 tokens 和密码
### 6.2 Token 权限最小化
- 只授予必需的权限
- 使用 fine-grained tokens(如果可用)
- 为不同用途创建不同的 tokens
### 6.3 审计日志
定期检查:
- GitHub Actions 运行日志
- Harbor 镜像推送记录
- ljwx-deploy 仓库的 commit 历史
## 7. 下一步
配置完成后:
1. ✅ 提交代码触发 CI/CD
2. ✅ 查看 GitHub Actions 运行状态
3. ✅ 验证镜像推送到 Harbor
4. ✅ 检查 ljwx-deploy 仓库的 PR
5. ✅ 合并 PR 触发 ArgoCD 部署
## 8. 参考资料
- **GitHub Actions 文档**: https://docs.github.com/en/actions
- **GitHub Secrets 文档**: https://docs.github.com/en/actions/security-guides/encrypted-secrets
- **Harbor 文档**: https://goharbor.io/docs/
- **Workflow 配置**: `.github/workflows/ci.yml`
- **架构文档**: `.github/workflows/README.md`
---
**最后更新**: 2026-02-26
**维护者**: DevOps Team
+105
View File
@@ -0,0 +1,105 @@
name: Backend Tests
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: wrongbook
POSTGRES_PASSWORD: wrongbook
POSTGRES_DB: wrongbook_test
ports:
- 5433:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6380:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
- name: Install dependencies
run: |
cd backend
pip install --upgrade pip
pip install -e ".[dev]"
- name: Run linter
run: |
cd backend
ruff check .
- name: Run type checker (optional)
continue-on-error: true
run: |
cd backend
pip install mypy
mypy app --ignore-missing-imports
- name: Run migrations
env:
DATABASE_URL: postgresql://wrongbook:wrongbook@localhost:5433/wrongbook_test
run: |
cd backend
alembic upgrade head
- name: Run tests with coverage
env:
TEST_DATABASE_URL: postgresql+asyncpg://wrongbook:wrongbook@localhost:5433/wrongbook_test
REDIS_URL: redis://localhost:6380/0
ANTHROPIC_AUTH_TOKEN: sk-ant-test-placeholder
ANTHROPIC_BASE_URL: https://api.anthropic.com
ANTHROPIC_MODEL: claude-sonnet-4-6
TESTING: "true"
JWT_SECRET: test-secret-key
run: |
cd backend
pytest --cov=app --cov-report=xml --cov-report=term --cov-fail-under=70 -v
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: backend/coverage.xml
flags: backend
name: backend-coverage
fail_ci_if_error: false
- name: Generate coverage badge
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
run: |
cd backend
pip install coverage-badge
coverage-badge -o coverage.svg -f
- name: Test summary
if: always()
run: |
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
echo "✅ Tests completed" >> $GITHUB_STEP_SUMMARY
+311
View File
@@ -0,0 +1,311 @@
name: CI/CD Pipeline
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
HARBOR_HOST: harbor.omniverseai.net
HARBOR_PROJECT: ljwx
IMAGE_NAME: ljwx-shiti
jobs:
# ── Job 1: Lint and Test ──────────────────────────────────────────────────
test:
name: Test Backend
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: wrongbook
POSTGRES_PASSWORD: wrongbook
POSTGRES_DB: wrongbook_test
ports:
- 5433:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6380:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
- name: Install dependencies
run: |
cd backend
pip install --upgrade pip
pip install -e ".[dev]"
- name: Run linter
run: |
cd backend
ruff check .
- name: Run type checker (optional)
continue-on-error: true
run: |
cd backend
pip install mypy
mypy app --ignore-missing-imports
- name: Run migrations
env:
DATABASE_URL: postgresql://wrongbook:wrongbook@localhost:5433/wrongbook_test
run: |
cd backend
alembic upgrade head
- name: Run tests with coverage
env:
TEST_DATABASE_URL: postgresql+asyncpg://wrongbook:wrongbook@localhost:5433/wrongbook_test
REDIS_URL: redis://localhost:6380/0
ANTHROPIC_AUTH_TOKEN: sk-ant-test-placeholder
ANTHROPIC_BASE_URL: https://api.anthropic.com
ANTHROPIC_MODEL: claude-sonnet-4-6
TESTING: "true"
JWT_SECRET: test-secret-key
run: |
cd backend
pytest --cov=app --cov-report=xml --cov-report=term --cov-fail-under=70 -v
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: backend/coverage.xml
flags: backend
name: backend-coverage
fail_ci_if_error: false
# ── Job 2: Build and Push Docker Image ────────────────────────────────────
build-and-push:
name: Build and Push Image
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
timeout-minutes: 30
outputs:
image_name: ${{ steps.meta.outputs.image_name }}
image_ref: ${{ steps.meta.outputs.image_ref }}
image_tag: ${{ steps.meta.outputs.image_tag }}
image_digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Resolve metadata
id: meta
shell: bash
run: |
set -euo pipefail
IMAGE_NAME="${HARBOR_HOST}/${HARBOR_PROJECT}/${IMAGE_NAME}"
IMAGE_TAG="${GITHUB_SHA:0:7}"
{
echo "image_name=${IMAGE_NAME}"
echo "image_ref=${IMAGE_NAME}:${IMAGE_TAG}"
echo "image_tag=${IMAGE_TAG}"
} >> "$GITHUB_OUTPUT"
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: ${{ env.HARBOR_HOST }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v6
id: build
with:
context: .
file: backend/Dockerfile
push: true
tags: |
${{ steps.meta.outputs.image_ref }}
${{ steps.meta.outputs.image_name }}:latest
cache-from: type=gha,scope=backend
cache-to: type=gha,scope=backend,mode=max
platforms: linux/amd64
# ── Job 3: Update Deploy Repository ───────────────────────────────────────
update-deploy:
name: Update Deploy Repo
runs-on: ubuntu-latest
needs: build-and-push
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install PyYAML
shell: bash
run: python3 -m pip install --quiet pyyaml
- name: Resolve deploy config
id: cfg
shell: bash
run: |
set -euo pipefail
{
echo "deploy_repo=BrunoGao/ljwx-deploy"
echo "overlay_file=apps/ljwx-shiti/overlays/prod/kustomization.yaml"
} >> "$GITHUB_OUTPUT"
- name: Clone deploy repo
env:
DEPLOY_REPO_TOKEN: ${{ secrets.DEPLOY_REPO_TOKEN }}
shell: bash
run: |
set -euo pipefail
if [ -z "${DEPLOY_REPO_TOKEN:-}" ]; then
echo "DEPLOY_REPO_TOKEN is required" && exit 1
fi
git clone \
"https://x-access-token:${DEPLOY_REPO_TOKEN}@github.com/${{ steps.cfg.outputs.deploy_repo }}.git" \
deploy-repo
- name: Update overlay kustomization
env:
OVERLAY_FILE: ${{ steps.cfg.outputs.overlay_file }}
IMAGE_NAME: ${{ needs.build-and-push.outputs.image_name }}
IMAGE_TAG: ${{ needs.build-and-push.outputs.image_tag }}
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import os
from pathlib import Path
import yaml
overlay = Path("deploy-repo") / os.environ["OVERLAY_FILE"]
# 如果文件不存在,创建基础结构
if not overlay.exists():
overlay.parent.mkdir(parents=True, exist_ok=True)
data = {
"apiVersion": "kustomize.config.k8s.io/v1beta1",
"kind": "Kustomization",
"namespace": "ljwx-shiti",
"resources": ["../../base"],
"images": []
}
else:
data = yaml.safe_load(overlay.read_text()) or {}
# 确保 images 字段存在
if "images" not in data:
data["images"] = []
# 更新或添加镜像配置
image_name = os.environ["IMAGE_NAME"]
image_tag = os.environ["IMAGE_TAG"]
found = False
for img in data["images"]:
if img.get("name") == image_name or img.get("newName") == image_name:
img["newTag"] = image_tag
found = True
break
if not found:
data["images"].append({
"name": image_name,
"newTag": image_tag
})
overlay.write_text(yaml.safe_dump(data, sort_keys=False))
print(f"Updated {overlay} with image tag: {image_tag}")
PY
- name: Create deploy PR
working-directory: deploy-repo
env:
GH_TOKEN: ${{ secrets.DEPLOY_REPO_TOKEN }}
GIT_SHA: ${{ github.sha }}
IMAGE_REF: ${{ needs.build-and-push.outputs.image_ref }}
IMAGE_DIGEST: ${{ needs.build-and-push.outputs.image_digest }}
shell: bash
run: |
set -euo pipefail
SHA_SHORT="${GIT_SHA:0:7}"
BRANCH="bot/release-ljwx-shiti-${GITHUB_RUN_ID}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b "${BRANCH}"
git add "${{ steps.cfg.outputs.overlay_file }}"
# 检查是否有变更
if git diff --staged --quiet; then
echo "No changes to commit"
exit 0
fi
git commit -m "release(ljwx-shiti): ${SHA_SHORT}"
git push origin "${BRANCH}"
gh pr create \
--repo "${{ steps.cfg.outputs.deploy_repo }}" \
--title "release(ljwx-shiti): ${SHA_SHORT}" \
--body "Automated release PR from ${{ github.repository }}@${GIT_SHA}
**Image** \`${IMAGE_REF}\`
digest: ${IMAGE_DIGEST}
**Changes**: ${{ github.event.head_commit.message }}
" \
--base main \
--head "${BRANCH}"
# ── Job 4: Test Summary ───────────────────────────────────────────────────
summary:
name: CI/CD Summary
runs-on: ubuntu-latest
needs: [test, build-and-push, update-deploy]
if: always()
steps:
- name: Generate summary
run: |
echo "## CI/CD Pipeline Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Test**: ${{ needs.test.result }}" >> $GITHUB_STEP_SUMMARY
echo "- **Build**: ${{ needs.build-and-push.result }}" >> $GITHUB_STEP_SUMMARY
echo "- **Deploy**: ${{ needs.update-deploy.result }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.build-and-push.result }}" == "success" ]; then
echo "**Image**: \`${{ needs.build-and-push.outputs.image_ref }}\`" >> $GITHUB_STEP_SUMMARY
fi
+316
View File
@@ -0,0 +1,316 @@
# GitHub Actions CI/CD 设置完成总结
## ✅ 已完成的工作
### 1. Dockerfile 优化
- ✅ 更新为使用 `pyproject.toml`
- ✅ 添加健康检查
- ✅ 使用非 root 用户运行
- ✅ 修复系统依赖包名(libgl1)
- 📁 文件: `backend/Dockerfile`
### 2. GitHub Actions Workflow
- ✅ 创建完整的 CI/CD pipeline
- ✅ 包含测试、构建、部署三个阶段
- ✅ 自动推送镜像到 Harbor
- ✅ 自动更新 ljwx-deploy 仓库
- 📁 文件: `.github/workflows/ci.yml`
### 3. 文档
- ✅ CI/CD 架构文档 (`.github/workflows/README.md`)
- ✅ Secrets 设置详细指南 (`.github/workflows/SECRETS_SETUP_GUIDE.md`)
- ✅ 快速开始指南 (`.github/workflows/QUICKSTART.md`)
- ✅ 设置指南 (`.github/workflows/SETUP.md`)
- ✅ 文档索引 (`.github/workflows/INDEX.md`)
### 4. 自动化脚本
- ✅ Secrets 设置脚本 (`scripts/setup-github-secrets.sh`)
- ✅ 本地 CI 验证脚本 (`scripts/verify-ci.sh`)
---
## 🚀 下一步操作
### 步骤 1: 设置 GitHub Secrets(必需)
运行交互式设置脚本:
```bash
./scripts/setup-github-secrets.sh
```
或手动设置(需要 3 个 secrets):
#### Secret 1: HARBOR_USERNAME
```bash
echo "admin" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
```
#### Secret 2: HARBOR_PASSWORD
```bash
# 会提示输入密码
gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
```
#### Secret 3: DEPLOY_REPO_TOKEN
**首先创建 GitHub Personal Access Token:**
1. 访问: https://github.com/settings/tokens
2. 点击 "Generate new token (classic)"
3. 名称: `ljwx-shiti-deploy-access`
4. 权限:
- ✅ `repo` (完整仓库访问)
- ✅ `workflow` (更新 workflow 文件)
5. 生成并复制 token
**然后设置 secret:**
```bash
# 会提示输入 token
gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
```
**验证 secrets:**
```bash
gh secret list --repo BrunoGao/ljwx-shiti
# 应该看到:
# HARBOR_USERNAME Updated 2026-02-26
# HARBOR_PASSWORD Updated 2026-02-26
# DEPLOY_REPO_TOKEN Updated 2026-02-26
```
---
### 步骤 2: 提交代码触发 CI/CD
```bash
# 添加所有新文件
git add .github/ backend/Dockerfile scripts/ docs/
# 提交
git commit -m "ci: add GitHub Actions CI/CD pipeline
- Add Dockerfile with health check and non-root user
- Add complete CI/CD workflow (test, build, deploy)
- Add documentation and setup scripts
- Add secrets setup guide"
# 推送到 GitHub(触发 CI/CD)
git push origin master
```
---
### 步骤 3: 监控 CI/CD 运行
#### 方式 A: 浏览器
访问: https://github.com/BrunoGao/ljwx-shiti/actions
#### 方式 B: GitHub CLI
```bash
# 查看运行列表
gh run list --repo BrunoGao/ljwx-shiti
# 实时监控
gh run watch --repo BrunoGao/ljwx-shiti
# 查看日志
gh run view --log --repo BrunoGao/ljwx-shiti
```
---
## 📊 CI/CD 流程说明
### 阶段 1: Test (约 20 分钟)
```
✅ Checkout code
✅ Setup Python 3.11
✅ Install dependencies
✅ Run linter (ruff)
✅ Run type checker (mypy)
✅ Run migrations (alembic)
✅ Run tests with coverage (pytest)
✅ Upload coverage to Codecov
```
### 阶段 2: Build and Push (约 30 分钟)
```
✅ Checkout code
✅ Setup Docker Buildx
✅ Login to Harbor
✅ Build Docker image
✅ Push to Harbor
- Image: harbor.omniverseai.net/ljwx/ljwx-shiti:<commit-sha>
- Tag: latest
```
### 阶段 3: Update Deploy (约 10 分钟)
```
✅ Clone ljwx-deploy repository
✅ Update kustomization.yaml
- Path: apps/ljwx-shiti/overlays/prod/kustomization.yaml
- Update image tag to <commit-sha>
✅ Create Pull Request
- Title: release(ljwx-shiti): <commit-sha>
- Base: main
```
### 阶段 4: ArgoCD 自动部署 (约 30 秒)
```
✅ ArgoCD 检测 Git 变更
✅ 应用 Kustomize 配置
✅ 滚动更新 Deployment
✅ 健康检查通过
✅ 应用运行在 K8s 集群
```
---
## 🔍 验证清单
### ✅ Secrets 配置
```bash
# 检查 secrets
gh secret list --repo BrunoGao/ljwx-shiti
# 应该有 3 个 secrets:
# - HARBOR_USERNAME
# - HARBOR_PASSWORD
# - DEPLOY_REPO_TOKEN
```
### ✅ Workflow 文件
```bash
# 检查 workflow 文件
cat .github/workflows/ci.yml
# 验证语法
gh workflow view ci.yml --repo BrunoGao/ljwx-shiti
```
### ✅ Harbor 访问
```bash
# 测试 Harbor 登录
docker login harbor.omniverseai.net
# 输入用户名和密码
# 应该看到: Login Succeeded
```
### ✅ Deploy Repo 访问
```bash
# 测试 token 权限
curl -H "Authorization: token YOUR_TOKEN" \
https://api.github.com/repos/BrunoGao/ljwx-deploy
# 应该返回仓库信息
```
---
## 🐛 故障排查
### 问题 1: Secrets 未设置
**症状**: Workflow 失败,提示 "Secret not found"
**解决**:
```bash
# 重新设置 secrets
./scripts/setup-github-secrets.sh
```
### 问题 2: Harbor 登录失败
**症状**: "Error response from daemon: unauthorized"
**解决**:
```bash
# 1. 验证本地登录
docker login harbor.omniverseai.net
# 2. 检查 secrets
gh secret list --repo BrunoGao/ljwx-shiti
# 3. 重新设置 Harbor secrets
echo "admin" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
```
### 问题 3: Deploy Repo 访问失败
**症状**: "Repository not found"
**解决**:
```bash
# 1. 测试 token
curl -H "Authorization: token YOUR_TOKEN" \
https://api.github.com/repos/BrunoGao/ljwx-deploy
# 2. 重新生成 token(确保有 repo 和 workflow 权限)
# 3. 更新 secret
gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
```
### 问题 4: 测试失败
**症状**: pytest 失败
**解决**:
```bash
# 1. 本地运行测试
cd backend
pytest -v
# 2. 检查测试日志
gh run view --log --repo BrunoGao/ljwx-shiti
# 3. 修复测试后重新推送
```
---
## 📚 参考文档
### 快速参考
- [QUICKSTART.md](.github/workflows/QUICKSTART.md) - 快速开始
- [INDEX.md](.github/workflows/INDEX.md) - 文档索引
### 详细指南
- [SECRETS_SETUP_GUIDE.md](.github/workflows/SECRETS_SETUP_GUIDE.md) - Secrets 设置
- [README.md](.github/workflows/README.md) - CI/CD 架构
- [SETUP.md](.github/workflows/SETUP.md) - 完整设置
### 脚本
- `scripts/setup-github-secrets.sh` - Secrets 设置脚本
- `scripts/verify-ci.sh` - 本地验证脚本
---
## 🎯 成功标志
当你看到以下内容时,说明 CI/CD 设置成功:
1. ✅ GitHub Actions 运行成功(绿色勾)
2. ✅ Harbor 中有新镜像: `ljwx-shiti:<commit-sha>`
3. ✅ ljwx-deploy 仓库有新 PR
4. ✅ ArgoCD 自动部署应用
5. ✅ 应用在 K8s 集群中运行
---
## 📞 获取帮助
如果遇到问题:
1. 查看 [故障排查](#-故障排查) 部分
2. 运行验证脚本: `./scripts/setup-github-secrets.sh`
3. 查看 GitHub Actions 日志: `gh run view --log`
4. 查看详细文档: `.github/workflows/SECRETS_SETUP_GUIDE.md`
5. 联系 DevOps 团队
---
**祝你成功!🎉**
**最后更新**: 2026-02-26
+38
View File
@@ -0,0 +1,38 @@
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
g++ \
postgresql-client \
libgl1 \
libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY backend/pyproject.toml .
# 安装 Python 依赖(不包含 paddle,生产环境降级到规则切分)
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -e .
# 复制应用代码
COPY backend/ .
# 创建非 root 用户
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 8000
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
# 启动命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
+292
View File
@@ -0,0 +1,292 @@
#!/bin/bash
# GitHub Secrets 设置脚本
set -euo pipefail
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
echo_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
echo_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
echo_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# 检查 gh CLI
check_gh_cli() {
if ! command -v gh &> /dev/null; then
echo_error "GitHub CLI (gh) 未安装"
echo_info "请访问: https://cli.github.com/ 安装"
exit 1
fi
if ! gh auth status &> /dev/null; then
echo_error "GitHub CLI 未登录"
echo_info "请运行: gh auth login"
exit 1
fi
echo_info "✅ GitHub CLI 已就绪"
}
# 显示当前 secrets
show_current_secrets() {
echo_step "检查当前 secrets..."
echo ""
local secrets=$(gh secret list --repo BrunoGao/ljwx-shiti 2>/dev/null || echo "")
if [ -z "$secrets" ]; then
echo_warn "当前没有配置任何 secrets"
else
echo "当前已配置的 secrets:"
echo "$secrets"
fi
echo ""
}
# 设置 Harbor 凭据
setup_harbor_secrets() {
echo_step "设置 Harbor 镜像仓库凭据"
echo ""
# HARBOR_USERNAME
echo -n "请输入 Harbor 用户名 (默认: admin): "
read -r harbor_username
harbor_username=${harbor_username:-admin}
# HARBOR_PASSWORD
echo -n "请输入 Harbor 密��: "
read -rs harbor_password
echo ""
if [ -z "$harbor_password" ]; then
echo_error "Harbor 密码不能为空"
return 1
fi
# 测试 Harbor 登录
echo_info "测试 Harbor 登录..."
if echo "$harbor_password" | docker login harbor.omniverseai.net -u "$harbor_username" --password-stdin &> /dev/null; then
echo_info "✅ Harbor 登录成功"
docker logout harbor.omniverseai.net &> /dev/null
else
echo_error "❌ Harbor 登录失败,请检查用户名和密码"
return 1
fi
# 设置 secrets
echo_info "设置 HARBOR_USERNAME..."
echo "$harbor_username" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
echo_info "设置 HARBOR_PASSWORD..."
echo "$harbor_password" | gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
echo_info "✅ Harbor secrets 设置完成"
echo ""
}
# 设置 Deploy Repo Token
setup_deploy_token() {
echo_step "设置部署仓库访问 Token"
echo ""
echo "需要创建一个 GitHub Personal Access Token (PAT)"
echo "Token 需要以下权限:"
echo " - repo (完整仓库访问)"
echo " - workflow (更新 workflow 文件)"
echo ""
echo "创建 Token 步骤:"
echo "1. 访问: https://github.com/settings/tokens"
echo "2. 点击 'Generate new token' → 'Generate new token (classic)'"
echo "3. 设置名称: ljwx-shiti-deploy-access"
echo "4. 选择权限: repo, workflow"
echo "5. 点击 'Generate token'"
echo "6. 复制生成的 token"
echo ""
echo -n "是否已经创建了 Token? (y/n): "
read -r has_token
if [ "$has_token" != "y" ]; then
echo_warn "请先创建 Token,然后重新运行此脚本"
return 1
fi
echo -n "请输入 GitHub Personal Access Token: "
read -rs deploy_token
echo ""
if [ -z "$deploy_token" ]; then
echo_error "Token 不能为空"
return 1
fi
# 测试 token 权限
echo_info "测试 Token 权限..."
if curl -s -H "Authorization: token $deploy_token" \
https://api.github.com/repos/BrunoGao/ljwx-deploy | grep -q '"name"'; then
echo_info "✅ Token 权限验证成功"
else
echo_error "❌ Token 权限验证失败,请检查 token 是否正确"
return 1
fi
# 设置 secret
echo_info "设置 DEPLOY_REPO_TOKEN..."
echo "$deploy_token" | gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
echo_info "✅ Deploy token 设置完成"
echo ""
}
# 验证所有 secrets
verify_secrets() {
echo_step "验证 secrets 配置"
echo ""
local required_secrets=("HARBOR_USERNAME" "HARBOR_PASSWORD" "DEPLOY_REPO_TOKEN")
local all_ok=true
for secret in "${required_secrets[@]}"; do
if gh secret list --repo BrunoGao/ljwx-shiti | grep -q "^$secret"; then
echo_info "✅ $secret 已配置"
else
echo_error "❌ $secret 未配置"
all_ok=false
fi
done
echo ""
if [ "$all_ok" = true ]; then
echo_info "=========================================="
echo_info "✅ 所有 secrets 配置完成!"
echo_info "=========================================="
return 0
else
echo_error "部分 secrets 未配置,请重新运行脚本"
return 1
fi
}
# 显示下一步
show_next_steps() {
echo ""
echo_info "下一步操作:"
echo ""
echo "1. 提交代码触发 CI/CD:"
echo " git add ."
echo " git commit -m 'ci: add CI/CD pipeline'"
echo " git push origin main"
echo ""
echo "2. 查看 GitHub Actions 运行状态:"
echo " https://github.com/BrunoGao/ljwx-shiti/actions"
echo ""
echo "3. 或使用命令行查看:"
echo " gh run list --repo BrunoGao/ljwx-shiti"
echo " gh run watch --repo BrunoGao/ljwx-shiti"
echo ""
}
# 主菜单
main_menu() {
while true; do
echo ""
echo "=========================================="
echo "GitHub Secrets 设置工具"
echo "=========================================="
echo ""
echo "1. 查看当前 secrets"
echo "2. 设置 Harbor 凭据 (HARBOR_USERNAME, HARBOR_PASSWORD)"
echo "3. 设置部署 Token (DEPLOY_REPO_TOKEN)"
echo "4. 一键设置所有 secrets"
echo "5. 验证 secrets 配置"
echo "6. 退出"
echo ""
echo -n "请选择操作 (1-6): "
read -r choice
case $choice in
1)
show_current_secrets
;;
2)
setup_harbor_secrets
;;
3)
setup_deploy_token
;;
4)
setup_harbor_secrets && setup_deploy_token && verify_secrets && show_next_steps
;;
5)
verify_secrets
;;
6)
echo_info "退出"
exit 0
;;
*)
echo_error "无效选择,请输入 1-6"
;;
esac
done
}
# 非交互模式
non_interactive_mode() {
echo_info "非交互模式"
echo ""
# 从环境变量读取
if [ -n "${HARBOR_USERNAME:-}" ] && [ -n "${HARBOR_PASSWORD:-}" ] && [ -n "${DEPLOY_REPO_TOKEN:-}" ]; then
echo_info "从环境变量读取 secrets..."
echo "$HARBOR_USERNAME" | gh secret set HARBOR_USERNAME --repo BrunoGao/ljwx-shiti
echo "$HARBOR_PASSWORD" | gh secret set HARBOR_PASSWORD --repo BrunoGao/ljwx-shiti
echo "$DEPLOY_REPO_TOKEN" | gh secret set DEPLOY_REPO_TOKEN --repo BrunoGao/ljwx-shiti
verify_secrets
show_next_steps
else
echo_error "环境变量未设置"
echo_info "请设置以下环境变量:"
echo " - HARBOR_USERNAME"
echo " - HARBOR_PASSWORD"
echo " - DEPLOY_REPO_TOKEN"
exit 1
fi
}
# 主函数
main() {
echo_info "GitHub Secrets 设置脚本"
echo ""
check_gh_cli
# 检查是否为非交互模式
if [ "${NON_INTERACTIVE:-false}" = "true" ]; then
non_interactive_mode
else
main_menu
fi
}
# 运行主函数
main "$@"
+298
View File
@@ -0,0 +1,298 @@
#!/bin/bash
# 本地 CI/CD 验证脚本
set -euo pipefail
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
echo_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
echo_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查必需的工具
check_requirements() {
echo_info "检查必需的工具..."
local missing_tools=()
if ! command -v docker &> /dev/null; then
missing_tools+=("docker")
fi
if ! command -v docker-compose &> /dev/null; then
missing_tools+=("docker-compose")
fi
if ! command -v python3 &> /dev/null; then
missing_tools+=("python3")
fi
if [ ${#missing_tools[@]} -ne 0 ]; then
echo_error "缺少必需的工具: ${missing_tools[*]}"
exit 1
fi
echo_info "✅ 所有必需工具已安装"
}
# 运行 linter
run_linter() {
echo_info "运行 linter..."
cd backend
if ! pip list | grep -q ruff; then
echo_info "安装 ruff..."
pip install ruff
fi
if ruff check .; then
echo_info "✅ Linter 检查通过"
else
echo_error "❌ Linter 检查失败"
exit 1
fi
cd ..
}
# 启动测试数据库
start_test_db() {
echo_info "启动测试数据库..."
# 检查是否已经运行
if docker ps | grep -q wrongbook-postgres-test; then
echo_warn "测试数据库已在运行"
return
fi
# 启动 PostgreSQL
docker run -d \
--name wrongbook-postgres-test \
-e POSTGRES_USER=wrongbook \
-e POSTGRES_PASSWORD=wrongbook \
-e POSTGRES_DB=wrongbook_test \
-p 5433:5432 \
postgres:16-alpine
# 启动 Redis
docker run -d \
--name wrongbook-redis-test \
-p 6380:6379 \
redis:7-alpine
echo_info "等待数据库启动..."
sleep 5
echo_info "✅ 测试数据库已启动"
}
# 停止测试数据库
stop_test_db() {
echo_info "停止测试数据库..."
docker stop wrongbook-postgres-test wrongbook-redis-test 2>/dev/null || true
docker rm wrongbook-postgres-test wrongbook-redis-test 2>/dev/null || true
echo_info "✅ 测试数据库已停止"
}
# 运行测试
run_tests() {
echo_info "运行测试..."
cd backend
# 安装依赖
if [ ! -d ".venv" ]; then
echo_info "创建虚拟环境..."
python3 -m venv .venv
fi
source .venv/bin/activate
echo_info "安装依赖..."
pip install -q --upgrade pip
pip install -q -e ".[dev]"
# 运行迁移
echo_info "运行数据库迁移..."
export DATABASE_URL="postgresql://wrongbook:wrongbook@localhost:5433/wrongbook_test"
alembic upgrade head
# 运行测试
echo_info "运行测试..."
export TEST_DATABASE_URL="postgresql+asyncpg://wrongbook:wrongbook@localhost:5433/wrongbook_test"
export REDIS_URL="redis://localhost:6380/0"
export ANTHROPIC_AUTH_TOKEN="sk-ant-test-placeholder"
export ANTHROPIC_BASE_URL="https://api.anthropic.com"
export ANTHROPIC_MODEL="claude-sonnet-4-6"
export TESTING="true"
export JWT_SECRET="test-secret-key"
if pytest -v --cov=app --cov-report=term; then
echo_info "✅ 测试通过"
else
echo_error "❌ 测试失败"
deactivate
cd ..
exit 1
fi
deactivate
cd ..
}
# 构建 Docker 镜像
build_docker_image() {
echo_info "构建 Docker 镜像..."
local image_tag="ljwx-shiti:local-test"
if docker build -t "$image_tag" -f backend/Dockerfile .; then
echo_info "✅ Docker 镜像构建成功: $image_tag"
else
echo_error "❌ Docker 镜像构建失败"
exit 1
fi
# 显示镜像信息
echo_info "镜像信息:"
docker images "$image_tag"
}
# 测试 Docker 镜像
test_docker_image() {
echo_info "测试 Docker 镜像..."
local image_tag="ljwx-shiti:local-test"
local container_name="ljwx-shiti-test"
# 停止并删除旧容器
docker stop "$container_name" 2>/dev/null || true
docker rm "$container_name" 2>/dev/null || true
# 启动容器
echo_info "启动测试容器..."
docker run -d \
--name "$container_name" \
-p 8000:8000 \
-e DATABASE_URL="postgresql+asyncpg://wrongbook:wrongbook@host.docker.internal:5433/wrongbook_test" \
-e REDIS_URL="redis://host.docker.internal:6380/0" \
-e ANTHROPIC_AUTH_TOKEN="sk-ant-test-placeholder" \
-e JWT_SECRET="test-secret-key" \
"$image_tag"
echo_info "等待应用启动..."
sleep 10
# 测试健康检查
echo_info "测试健康检查端点..."
if curl -f http://localhost:8000/health; then
echo_info "✅ 健康检查通过"
else
echo_error "❌ 健康检查失败"
docker logs "$container_name"
docker stop "$container_name"
docker rm "$container_name"
exit 1
fi
# 清理
echo_info "清理测试容器..."
docker stop "$container_name"
docker rm "$container_name"
echo_info "✅ Docker 镜像测试通过"
}
# 清理
cleanup() {
echo_info "清理资源..."
stop_test_db
echo_info "✅ 清理完成"
}
# 主函数
main() {
echo_info "开始本地 CI/CD 验证..."
echo ""
# 设置清理 trap
trap cleanup EXIT
# 检查参数
local skip_tests=false
local skip_docker=false
while [[ $# -gt 0 ]]; do
case $1 in
--skip-tests)
skip_tests=true
shift
;;
--skip-docker)
skip_docker=true
shift
;;
--help)
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " --skip-tests 跳过测试"
echo " --skip-docker 跳过 Docker 构建和测试"
echo " --help 显示帮助信息"
exit 0
;;
*)
echo_error "未知选项: $1"
exit 1
;;
esac
done
# 执行验证步骤
check_requirements
if [ "$skip_tests" = false ]; then
run_linter
start_test_db
run_tests
else
echo_warn "跳过测试"
fi
if [ "$skip_docker" = false ]; then
build_docker_image
if [ "$skip_tests" = false ]; then
test_docker_image
fi
else
echo_warn "跳过 Docker 构建和测试"
fi
echo ""
echo_info "=========================================="
echo_info "✅ 本地 CI/CD 验证完成!"
echo_info "=========================================="
echo ""
echo_info "下一步:"
echo_info "1. 提交代码: git add . && git commit -m 'ci: add CI/CD pipeline'"
echo_info "2. 推送到 GitHub: git push origin main"
echo_info "3. 查看 GitHub Actions: https://github.com/BrunoGao/ljwx-shiti/actions"
}
# 运行主函数
main "$@"