# ============================================ # Novalon Website - 全自动CI/CD工作流 # ============================================ # 发布策略:release分支发布 + main分支归档 # # 分支角色: # - feature分支:开发新功能 # - release分支:生产环境代码,合并后自动部署 # - main分支:稳定代码归档,只读 # # 流水线阶段: # 1. 代码质量检查 (lint, type-check, security) # 2. 单元测试和集成测试 # 3. E2E测试 (分层测试) # 4. 构建Docker镜像 # 5. 部署到生产环境 (release分支) # 6. 归档到main分支 # 7. 通知和监控 # ============================================ # 全局环境变量 variables: - &node_image node:20-alpine - &docker_image docker:24-cli # ============================================ # 阶段1: 代码质量检查 # ============================================ steps: # 1.1 Lint检查 lint: image: *node_image environment: NODE_ENV: development commands: - npm ci - npm run lint when: event: - push - pull_request # 1.2 类型检查 type-check: image: *node_image environment: NODE_ENV: development commands: - npm ci - npm run type-check when: event: - push - pull_request # 1.3 安全漏洞扫描 security-scan: image: *node_image environment: NODE_ENV: development commands: - npm ci - npm audit --audit-level=moderate when: event: - push - pull_request failure: ignore # ============================================ # 阶段2: 单元测试和集成测试 # ============================================ unit-tests: image: *node_image environment: NODE_ENV: test CI: true commands: - npm ci - npm run test:coverage:check when: event: - push - pull_request # ============================================ # 阶段3: E2E测试 (分层测试) # ============================================ # 3.1 Smoke测试 (PR快速验证) e2e-smoke: image: mcr.microsoft.com/playwright:v1.48.0-jammy environment: NODE_ENV: test CI: true commands: - npm ci - cd e2e && npm ci - npx playwright install chromium --with-deps - npm run test:smoke when: event: - pull_request # 3.2 标准测试 (release分支) e2e-standard: image: mcr.microsoft.com/playwright:v1.48.0-jammy environment: NODE_ENV: test CI: true commands: - npm ci - cd e2e && npm ci - npx playwright install chromium --with-deps - npm run test:tier:standard when: event: - push branch: - release - release/** # 3.3 深度测试 (release分支) e2e-deep: image: mcr.microsoft.com/playwright:v1.48.0-jammy environment: NODE_ENV: test CI: true commands: - npm ci - cd e2e && npm ci - npx playwright install chromium firefox webkit --with-deps - npm run test:tier:deep when: event: - push branch: - release - release/** # 3.4 性能测试 (release分支) e2e-performance: image: mcr.microsoft.com/playwright:v1.48.0-jammy environment: NODE_ENV: test CI: true commands: - npm ci - cd e2e && npm ci - npx playwright install chromium --with-deps - npm run test:performance when: event: - push branch: - release - release/** # 3.5 可访问性测试 (release分支) e2e-accessibility: image: mcr.microsoft.com/playwright:v1.48.0-jammy environment: NODE_ENV: test CI: true commands: - npm ci - cd e2e && npm ci - npx playwright install chromium --with-deps - npx playwright test --grep @accessibility when: event: - push branch: - release - release/** # 3.6 视觉回归测试 (release分支) e2e-visual: image: mcr.microsoft.com/playwright:v1.48.0-jammy environment: NODE_ENV: test CI: true commands: - npm ci - cd e2e && npm ci - npx playwright install chromium --with-deps - npx playwright test --grep @visual when: event: - push branch: - release - release/** # ============================================ # 阶段4: 构建Docker镜像 (release分支) # ============================================ build-image: image: *docker_image environment: DOCKER_HOST: tcp://docker:2375 REGISTRY_PASSWORD: from_secret: registry_password commands: - echo "Building Docker image..." - docker build -t registry.f.novalon.cn/novalon-website:${CI_COMMIT_SHA} . - docker tag registry.f.novalon.cn/novalon-website:${CI_COMMIT_SHA} registry.f.novalon.cn/novalon-website:latest - docker tag registry.f.novalon.cn/novalon-website:${CI_COMMIT_SHA} registry.f.novalon.cn/novalon-website:release-${CI_COMMIT_SHA:0:7} - echo "Pushing to registry..." - echo "$REGISTRY_PASSWORD" | docker login -u novalon-admin --password-stdin registry.f.novalon.cn - docker push registry.f.novalon.cn/novalon-website:${CI_COMMIT_SHA} - docker push registry.f.novalon.cn/novalon-website:latest - docker push registry.f.novalon.cn/novalon-website:release-${CI_COMMIT_SHA:0:7} volumes: - /var/run/docker.sock:/var/run/docker.sock when: - event: push branch: - release - release/** # ============================================ # 阶段5: 部署到生产环境 (release分支) # ============================================ deploy-production: image: alpine:latest environment: DEPLOY_ENV: production SSH_PRIVATE_KEY: from_secret: ssh_private_key REGISTRY_PASSWORD: from_secret: registry_password commands: - echo "Deploying to production environment..." - apk add --no-cache openssh-client curl - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H 139.155.109.62 >> ~/.ssh/known_hosts # 前置检查 - echo "Pre-deployment checks..." - ssh root@139.155.109.62 "echo 'Server connection OK'" - ssh root@139.155.109.62 "df -h | grep -E '/$|/home'" - ssh root@139.155.109.62 "docker ps | grep novalon-website || echo 'No existing container'" # 部署 - | ssh root@139.155.109.62 << EOF set -e # 任何命令失败立即退出 cd /home/novalon/docker-app/novalon-website echo "=== Step 1: Login to Registry ===" if ! echo "${REGISTRY_PASSWORD}" | docker login -u novalon-admin --password-stdin registry.f.novalon.cn; then echo "❌ Registry login failed!" exit 1 fi echo "=== Step 2: Backup current version ===" BACKUP_TIME=\$(date +%Y%m%d_%H%M%S) docker tag registry.f.novalon.cn/novalon-website:latest registry.f.novalon.cn/novalon-website:backup-\${BACKUP_TIME} 2>/dev/null || echo "No existing image to backup" echo "=== Step 3: Pull new image ===" if ! docker-compose pull novalon-website; then echo "❌ Image pull failed!" exit 1 fi echo "=== Step 4: Rolling update ===" docker-compose up -d --no-deps novalon-website echo "=== Step 5: Wait for service startup ===" sleep 10 echo "=== Step 6: Database migration ===" if ! docker-compose exec -T novalon-website npm run db:migrate; then echo "❌ Database migration failed, rolling back..." docker tag registry.f.novalon.cn/novalon-website:backup-\${BACKUP_TIME} registry.f.novalon.cn/novalon-website:latest 2>/dev/null || true docker-compose pull novalon-website docker-compose up -d --no-deps novalon-website exit 1 fi echo "=== Step 7: Health check ===" for i in {1..30}; do if curl -f https://novalon.cn/api/health; then echo "✅ Health check passed!" echo "=== Step 8: Cleanup old images ===" docker image prune -f docker images registry.f.novalon.cn/novalon-website --format "{{.ID}} {{.CreatedAt}}" | tail -n +4 | awk '{print \$1}' | xargs -r docker rmi -f || true exit 0 fi echo "Waiting for service to be ready... (\$i/30)" sleep 2 done echo "❌ Health check failed, rolling back..." docker tag registry.f.novalon.cn/novalon-website:backup-\${BACKUP_TIME} registry.f.novalon.cn/novalon-website:latest 2>/dev/null || true docker-compose pull novalon-website docker-compose up -d --no-deps novalon-website sleep 10 # 验证回滚 if curl -f https://novalon.cn/api/health; then echo "✅ Rollback succeeded, but deployment failed" else echo "❌ Rollback also failed!" fi exit 1 EOF - echo "✅ Production deployment completed!" when: event: - push branch: - release - release/** # ============================================ # 阶段6: 归档到main分支 (release分支) # ============================================ archive-to-main: image: alpine:latest environment: SSH_PRIVATE_KEY: from_secret: ssh_private_key commands: - echo "Archiving to main branch..." - apk add --no-cache git openssh-client - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H git.f.novalon.cn >> ~/.ssh/known_hosts - | set -e git config --global user.email "ci@novalon.cn" git config --global user.name "Woodpecker CI" # 使用SSH而不是HTTPS+Token git remote set-url origin git@git.f.novalon.cn:novalon/novalon-website.git # 拉取最新代码 git fetch origin git checkout main git pull origin main # 合并release分支 git merge release --no-ff -m "chore: 归档release ${CI_COMMIT_SHA:0:7}" # 创建版本标签 VERSION_TAG="v$(date +%Y.%m.%d)-${CI_COMMIT_SHA:0:7}" git tag -a "$VERSION_TAG" -m "Release $(date +%Y-%m-%d)" # 推送到远程(带重试) for i in {1..3}; do if git push origin main && git push origin --tags; then echo "✅ Archive succeeded! Version: $VERSION_TAG" exit 0 fi echo "Retry $i/3..." sleep 5 done echo "⚠️ Archive failed, but deployment succeeded" echo "Manual archive may be needed" exit 0 # 不阻止部署成功 when: event: - push branch: - release - release/** status: - success # ============================================ # 服务配置 # ============================================ services: docker: image: docker:24-dind privileged: true environment: DOCKER_TLS_CERTDIR: "" # ============================================ # 工作区配置 # ============================================ workspace: base: /woodpecker path: src # ============================================ # 克隆配置 # ============================================ clone: git: image: woodpeckerci/plugin-git settings: depth: 1 partial: false