From c0b561b6cb183cbb2b594241d09d51965b7120d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B9=9B=E5=85=AE?= Date: Fri, 5 Jun 2026 15:08:11 +0800 Subject: [PATCH] chore: move Jenkins pipeline config out of repo --- Jenkinsfile | 122 ------------------------------- Jenkinsfile.prod | 105 -------------------------- README.md | 23 ++---- deploy/jenkins/deploy-backend.sh | 74 ------------------- docs/ENVIRONMENT_DEPLOYMENT.md | 100 ------------------------- 5 files changed, 6 insertions(+), 418 deletions(-) delete mode 100644 Jenkinsfile delete mode 100644 Jenkinsfile.prod delete mode 100644 deploy/jenkins/deploy-backend.sh delete mode 100644 docs/ENVIRONMENT_DEPLOYMENT.md diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 6f4603d..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,122 +0,0 @@ -def isProductionDeploy() { - return params.DEPLOY_ENV == "production" -} - -def isTestDeploy() { - def branch = env.BRANCH_NAME ?: "" - return params.DEPLOY_ENV == "test" && !params.SKIP_DEPLOY && branch == env.TEST_BRANCH -} - -@NonCPS -boolean isUserTriggeredBuild() { - return currentBuild.rawBuild.getCauses().any { cause -> - cause.class.name == "hudson.model.Cause\$UserIdCause" - } -} - -pipeline { - agent any - - options { - timestamps() - disableConcurrentBuilds() - buildDiscarder(logRotator(numToKeepStr: "30", artifactNumToKeepStr: "10")) - } - - parameters { - choice( - name: "DEPLOY_ENV", - choices: ["test", "production"], - description: "test: develop 合并后自动部署测试环境;production: 仅允许手动输入 Tag 部署" - ) - string(name: "RELEASE_TAG", defaultValue: "", description: "生产部署必填,必须是 Gitea 中已存在的 Tag") - booleanParam(name: "SKIP_DEPLOY", defaultValue: false, description: "只构建检查,不执行部署") - } - - environment { - PROJECT_NAME = "access-manage" - TEST_BRANCH = "develop" - DEPLOY_BASE_DIR = "/srv/www" - } - - stages { - stage("Validate deploy policy") { - steps { - script { - if (isProductionDeploy()) { - if (!isUserTriggeredBuild()) { - error("生产环境禁止自动触发,只能在 Jenkins 手动 Build With Parameters。") - } - if (!params.RELEASE_TAG?.trim()) { - error("生产环境部署必须填写 RELEASE_TAG,且只能从项目 Tag 部署。") - } - } - - if (params.DEPLOY_ENV == "test" && env.BRANCH_NAME && env.BRANCH_NAME != env.TEST_BRANCH) { - echo "当前分支 ${env.BRANCH_NAME} 不是 ${env.TEST_BRANCH},本次只构建检查,不自动部署测试环境。" - } - } - } - } - - stage("Checkout production tag") { - when { - expression { isProductionDeploy() } - } - steps { - sh ''' - set -euo pipefail - git fetch --tags --force origin '+refs/tags/*:refs/tags/*' - tag_commit="$(git rev-parse -q --verify "refs/tags/${RELEASE_TAG}^{commit}")" - if [ -z "${tag_commit}" ]; then - echo "Tag not found: ${RELEASE_TAG}" >&2 - exit 1 - fi - git checkout -f "${tag_commit}" - git log -1 --oneline - ''' - } - } - - stage("Install") { - steps { - sh ''' - corepack enable || true - pnpm install --frozen-lockfile - ''' - } - } - - stage("Verify") { - steps { - sh "pnpm typecheck" - } - } - - stage("Build") { - steps { - script { - sh isProductionDeploy() ? "pnpm build:pro" : "pnpm build:test" - } - } - } - - stage("Deploy test") { - when { - expression { isTestDeploy() } - } - steps { - sh "bash deploy/jenkins/deploy-backend.sh test" - } - } - - stage("Deploy production") { - when { - expression { isProductionDeploy() && !params.SKIP_DEPLOY } - } - steps { - sh "bash deploy/jenkins/deploy-backend.sh production" - } - } - } -} diff --git a/Jenkinsfile.prod b/Jenkinsfile.prod deleted file mode 100644 index b907c45..0000000 --- a/Jenkinsfile.prod +++ /dev/null @@ -1,105 +0,0 @@ -pipeline { - agent any - - parameters { - gitParameter( - name: 'RELEASE_TAG', - type: 'PT_TAG', - tagFilter: 'v*', - branchFilter: 'origin/(.*)', - sortMode: 'DESCENDING_SMART', - selectedValue: 'TOP', - useRepository: 'http://127.0.0.1:3001/my-project/access-manage.git', - quickFilterEnabled: false, - listSize: '10', - requiredParameter: true, - description: '请选择要部署到生产环境的 Git Tag。列表自动来自当前项目仓库,生产只能从 Tag 发布。' - ) - } - - environment { - CI = 'true' - } - - options { - timestamps() - disableConcurrentBuilds() - buildDiscarder(logRotator(numToKeepStr: '10')) - } - - stages { - stage('Checkout Tag') { - steps { - checkout([ - $class: 'GitSCM', - branches: [[name: '*/master']], - userRemoteConfigs: [[ - url: 'http://127.0.0.1:3001/my-project/access-manage.git' - ]] - ]) - sh ''' - set -eu - test -n "$RELEASE_TAG" - NORMALIZED_RELEASE_TAG="$(printf '%s' "$RELEASE_TAG" | sed 's/\\^{}$//')" - case "$NORMALIZED_RELEASE_TAG" in - v[0-9A-Za-z._-]*) ;; - *) echo "Invalid release tag: $NORMALIZED_RELEASE_TAG"; exit 2 ;; - esac - if ! git ls-remote --exit-code --tags origin "refs/tags/$NORMALIZED_RELEASE_TAG" >/dev/null; then - echo "Release tag does not exist in repository: $NORMALIZED_RELEASE_TAG" - exit 3 - fi - echo "Deploying production tag: $NORMALIZED_RELEASE_TAG" - git fetch --tags --force - git checkout -f "refs/tags/$NORMALIZED_RELEASE_TAG" - git describe --tags --exact-match HEAD - ''' - } - } - - stage('Env') { - steps { - sh ''' - git --version - node -v - corepack --version - corepack enable - corepack prepare pnpm@11.5.0 --activate - pnpm -v - ''' - } - } - - stage('Install') { - steps { - sh ''' - pnpm install --frozen-lockfile - ''' - } - } - - stage('Check') { - steps { - sh ''' - pnpm typecheck - ''' - } - } - - stage('Build') { - steps { - sh ''' - pnpm build:pro - test -f dist/server.js - test -f dist/db/migrate.js - ''' - } - } - - stage('Deploy Production') { - steps { - sh 'sudo /usr/local/bin/deploy-access-manage-from-jenkins "$WORKSPACE"' - } - } - } -} diff --git a/README.md b/README.md index d357aa0..ba705ed 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,11 @@ ├── AGENTS.md # Codex/Agent 入口指令,当前指向 RTK.md ├── RTK.md # 项目协作规则和开发约定 ├── docs/ -│ ├── API.md # 前端对接接口文档 -│ └── ENVIRONMENT_DEPLOYMENT.md # 测试/生产环境拆分与 Jenkins 规则 +│ └── API.md # 前端对接接口文档 ├── deploy/ │ ├── env/ │ │ ├── test.env.example # 测试环境变量示例,不包含真实密码 │ │ └── production.env.example # 生产环境变量示例,不包含真实密码 -│ ├── jenkins/ -│ │ └── deploy-backend.sh # Jenkins 后端部署脚本 │ └── server/ │ ├── create-env.sh # 在服务器生成测试/生产真实环境变量 │ ├── docker-compose.mysql.test.yml # 测试 MySQL Compose 模板 @@ -85,7 +82,6 @@ │ │ └── tasks/ # 任务后台管理和员工端任务模块 │ └── shared/ # 通用响应结构和业务错误 ├── docker-compose.yml # 本地 MySQL -├── Jenkinsfile # Jenkins 测试自动部署、生产手动 Tag 部署规则 ├── package.json ├── pnpm-lock.yaml ├── README.md @@ -103,9 +99,7 @@ | `AGENTS.md` | Agent 工具读取的入口文件,当前通过 `@RTK.md` 引入项目规则。 | | `RTK.md` | 本项目的协作规则,例如使用中文说明、保持分层、改目录时同步 README。 | | `docs/API.md` | 面向前端对接的完整接口文档,包含认证、权限、字段约束、示例请求响应和错误码。 | -| `docs/ENVIRONMENT_DEPLOYMENT.md` | 测试/生产环境拆分、Jenkins 参数、Tag 发布和服务器路径约定。 | | `deploy/env/` | 测试/生产环境变量示例,只给字段结构,不提交真实密码。 | -| `deploy/jenkins/` | Jenkins 部署脚本。当前 `deploy-backend.sh` 会按环境发布到独立目录。 | | `deploy/server/` | 服务器部署辅助文件。`create-env.sh` 在服务器本地生成测试/生产真实环境变量,Compose 模板分别启动测试和生产 MySQL。 | | `migrations/` | 数据库迁移目录。所有建表、改表、初始化基础数据的 SQL 都放在这里。 | | `src/app.ts` | 创建 Fastify 应用,注册路由,处理健康检查和全局错误。 | @@ -124,7 +118,6 @@ | `src/modules/tasks/` | 任务模块,负责后台任务管理、员工端任务处理和任务事件日志。 | | `src/shared/` | 跨模块复用的响应结构和业务错误类型。 | | `docker-compose.yml` | 本地开发用 MySQL 容器配置。 | -| `Jenkinsfile` | 流水线入口。`develop` 合并自动部署测试环境;生产环境只能手动选择 Tag 部署。 | | `package.json` | 项目信息、依赖和常用脚本;脚本会读取现有 `.env.development`。 | | `pnpm-lock.yaml` | pnpm 锁文件,保证依赖版本一致。 | | `tsconfig.json` | TypeScript 编译配置。 | @@ -314,7 +307,7 @@ curl http://127.0.0.1:3500/health # 生产 只有返回里出现 `database: "up"`,才代表后端服务和 MySQL 都连通。 -如果手动压缩部署,至少需要包含 `dist/`、`migrations/`、`deploy/`、`package.json` 和 `pnpm-lock.yaml`。不要把本机 `.env.*`、`node_modules/`、`src/`、`tsconfig.json` 和 `.git/` 打进部署包。 +如果手动压缩部署,至少需要包含 `dist/`、`migrations/`、`package.json` 和 `pnpm-lock.yaml`。不要把本机 `.env.*`、`node_modules/`、`src/`、`tsconfig.json` 和 `.git/` 打进部署包。 可以在本机执行: @@ -324,13 +317,9 @@ pnpm build:dev 命令执行完成后会得到最新的 `dist/`。如果需要压缩上传,由部署者手动选择要打包的文件;测试构建可执行 `pnpm build:test`,正式生产构建时执行 `pnpm build:pro`。 -## Jenkins 环境规则 +## 环境构建约定 -流水线规则见 [docs/ENVIRONMENT_DEPLOYMENT.md](./docs/ENVIRONMENT_DEPLOYMENT.md)。 - -- 测试环境:`develop` 合并后自动触发,部署到 `/srv/www/test/access-manage/current`。 -- 生产环境:禁止代码合并自动触发,只能在 Jenkins 手动选择 `DEPLOY_ENV=production` 并填写 Gitea 项目 Tag。 -- 生产部署会先 checkout 到 `RELEASE_TAG` 对应的提交,再构建和部署。 +仓库只维护项目自身的构建脚本和环境变量示例,不维护流水线文件。测试、生产的触发规则和部署路径由外部 CI/CD 平台配置。 ## package.json 脚本说明 @@ -341,8 +330,8 @@ pnpm build:dev | `pnpm dev` | 使用现有 `.env.development` 启动开发服务,并通过 `tsx watch` 监听代码变化。 | 日常开发接口时使用。 | | `pnpm build` | 使用 `tsc` 编译 TypeScript,输出到 `dist/`。 | 准备运行编译产物或发布前验证时使用。 | | `pnpm build:dev` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | 准备开发/测试服务器部署产物时使用。 | -| `pnpm build:test` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | Jenkins 测试环境构建时使用。 | -| `pnpm build:pro` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | 准备正式生产部署产物时使用。 | +| `pnpm build:test` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | 准备测试环境部署产物时使用。 | +| `pnpm build:pro` | 清空旧 `dist/`,再执行 `pnpm build` 生成新的 `dist/`。 | 准备生产环境部署产物时使用。 | | `pnpm start` | 使用现有 `.env.development` 运行 `dist/server.js`。 | 已经执行过 `pnpm build` 后,用编译产物启动服务。 | | `pnpm start:test` | 使用 `.env.test` 运行 `dist/server.js`。 | 服务器测试环境启动编译产物时使用。 | | `pnpm start:prod` | 使用 `.env.production` 运行 `dist/server.js`。 | 服务器生产环境启动编译产物时使用。 | diff --git a/deploy/jenkins/deploy-backend.sh b/deploy/jenkins/deploy-backend.sh deleted file mode 100644 index 8353c1c..0000000 --- a/deploy/jenkins/deploy-backend.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -deploy_env="${1:?Usage: deploy-backend.sh test|production}" - -case "${deploy_env}" in - test) - env_file=".env.test" - ;; - production) - env_file=".env.production" - ;; - *) - echo "Unknown deploy environment: ${deploy_env}" >&2 - exit 1 - ;; -esac - -project_name="${PROJECT_NAME:-access-manage}" -base_dir="${DEPLOY_BASE_DIR:-/srv/www}" -target_dir="${DEPLOY_TARGET_DIR:-${base_dir}/${deploy_env}/${project_name}}" -deploy_remote="${DEPLOY_REMOTE:-}" -release_id="${BUILD_NUMBER:-manual}-$(git rev-parse --short=12 HEAD 2>/dev/null || date +%Y%m%d%H%M%S)" -package_dir=".deploy/${project_name}" - -rm -rf "${package_dir}" -mkdir -p "${package_dir}" - -cp -R dist "${package_dir}/dist" -cp -R migrations "${package_dir}/migrations" -cp -R deploy "${package_dir}/deploy" -cp package.json pnpm-lock.yaml "${package_dir}/" - -remote_shell() { - if [[ -n "${deploy_remote}" ]]; then - ssh "${deploy_remote}" "$@" - else - bash -lc "$*" - fi -} - -remote_copy() { - local source_dir="$1" - local dest_dir="$2" - - if [[ -n "${deploy_remote}" ]]; then - rsync -az --delete "${source_dir}/" "${deploy_remote}:${dest_dir}/" - else - mkdir -p "${dest_dir}" - rsync -az --delete "${source_dir}/" "${dest_dir}/" - fi -} - -release_dir="${target_dir}/releases/${release_id}" -shared_dir="${target_dir}/shared" - -remote_shell "mkdir -p '${release_dir}' '${shared_dir}' '${target_dir}/logs'" -remote_copy "${package_dir}" "${release_dir}" -remote_shell " - set -euo pipefail - if [ -f '${shared_dir}/${env_file}' ]; then - ln -sfn '../../shared/${env_file}' '${release_dir}/${env_file}' - else - echo 'Missing ${shared_dir}/${env_file}; create it before starting the service.' >&2 - fi - ln -sfn '${release_dir}' '${target_dir}/current' -" - -if [[ -n "${DEPLOY_POST_DEPLOY_CMD:-}" ]]; then - remote_shell "cd '${target_dir}/current' && ${DEPLOY_POST_DEPLOY_CMD}" -else - echo "Deployed ${project_name} ${deploy_env} to ${target_dir}/current" - echo "Set DEPLOY_POST_DEPLOY_CMD in Jenkins to install production dependencies, run migrations, and restart the service." -fi diff --git a/docs/ENVIRONMENT_DEPLOYMENT.md b/docs/ENVIRONMENT_DEPLOYMENT.md deleted file mode 100644 index c62733e..0000000 --- a/docs/ENVIRONMENT_DEPLOYMENT.md +++ /dev/null @@ -1,100 +0,0 @@ -# 环境拆分与流水线规则 - -本项目拆分为测试环境和生产环境两套独立环境。两套环境使用不同部署目录、环境变量文件、数据库端口和数据目录,避免测试迭代影响生产。 - -## 环境约定 - -| 环境 | 触发方式 | 代码依据 | 默认部署目录 | 默认应用端口 | 默认数据库端口 | 数据目录 | -| --- | --- | --- | --- | --- | --- | --- | -| 测试环境 | `develop` 合并后自动触发 Jenkins | `develop` 最新提交 | `/srv/www/test/access-manage/current` | `3501` | `3308` | `/srv/data/test/access-manage/mysql` | -| 生产环境 | Jenkins 手动触发 | Gitea 项目 Tag | `/srv/www/production/access-manage/current` | `3500` | `3307` | `/srv/data/production/access-manage/mysql` | - -生产环境禁止因代码合并自动部署。生产部署时必须在 Jenkins 参数中选择 `DEPLOY_ENV=production` 并填写已存在的 `RELEASE_TAG`。 - -## Jenkins 参数 - -| 参数 | 说明 | -| --- | --- | -| `DEPLOY_ENV` | `test` 或 `production`。默认 `test`。 | -| `RELEASE_TAG` | 生产环境必填,必须是 Gitea 仓库中已存在的 Tag。 | -| `SKIP_DEPLOY` | 为 `true` 时只执行安装、检查、构建,不部署。 | - -`Jenkinsfile` 会强制校验: - -- 测试环境只在 `develop` 分支自动部署。 -- 生产环境必须手动触发。 -- 生产环境必须填写 `RELEASE_TAG`。 -- 生产环境构建会先 checkout 到该 Tag 对应的提交,再部署。 - -## 服务器环境变量 - -测试和生产的真实环境变量必须留在服务器,不提交到仓库。 - -生成测试环境: - -```bash -cd /srv/www/test/access-manage -bash deploy/server/create-env.sh /srv/www/test/access-manage test -docker-compose --env-file .env.test.mysql -f deploy/server/docker-compose.mysql.test.yml up -d mysql -``` - -生成生产环境: - -```bash -cd /srv/www/production/access-manage -bash deploy/server/create-env.sh /srv/www/production/access-manage production -docker-compose --env-file .env.production.mysql -f deploy/server/docker-compose.mysql.production.yml up -d mysql -``` - -部署脚本默认会读取: - -```text -/srv/www/test/access-manage/shared/.env.test -/srv/www/production/access-manage/shared/.env.production -``` - -如果使用 `create-env.sh` 在 `current` 外生成了 env 文件,请把对应文件放到 `shared/` 下,或者在服务器上建立等价软链。 - -## 生产发布流程 - -1. 在需要发布的提交上创建 Tag。 -2. 推送 Tag 到 Gitea。 -3. Jenkins 手动 Build With Parameters。 -4. 选择 `DEPLOY_ENV=production`。 -5. 填写 `RELEASE_TAG`。 -6. 执行构建部署。 - -示例: - -```bash -git tag -a v2026.06.05-1 -m "access-manage production release 2026-06-05" -git push origin v2026.06.05-1 -``` - -## 部署脚本 - -`deploy/jenkins/deploy-backend.sh` 会把 `dist/`、`migrations/`、`deploy/`、`package.json` 和 `pnpm-lock.yaml` 发布到: - -```text -${DEPLOY_BASE_DIR}/${DEPLOY_ENV}/access-manage/releases/- -``` - -并更新: - -```text -${DEPLOY_BASE_DIR}/${DEPLOY_ENV}/access-manage/current -``` - -如果 Jenkins 不在目标服务器上运行,可以配置: - -```text -DEPLOY_REMOTE=user@server -``` - -如果需要部署后自动安装依赖、执行迁移和重启服务,在 Jenkins 中配置: - -```text -DEPLOY_POST_DEPLOY_CMD=pnpm install --prod --frozen-lockfile && pnpm db:migrate:prod && systemctl restart access-manage-production -``` - -测试环境应配置独立命令,例如使用 `.env.test` 和独立服务名。