From 18c14f18afd72dcd5540a0e5089d3859a1da1f1d Mon Sep 17 00:00:00 2001 From: qihongkun Date: Mon, 25 May 2026 14:42:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20Jenkins=20=E6=B5=81=E6=B0=B4?= =?UTF-8?q?=E7=BA=BF=E6=94=B9=E7=94=A8=20Publish=20Over=20SSH=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 sshagent + rsync 方式替换为 sshPublisher 方式 - 移除手动 SSH 参数配置,使用固定服务器配置 - 更新默认管理员密码 Co-authored-by: Cursor --- .env.example | 2 +- Jenkinsfile | 243 +++++++++++++++++++++++++-------------------------- 2 files changed, 121 insertions(+), 124 deletions(-) diff --git a/.env.example b/.env.example index 0b8c5b9..23cc91e 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,7 @@ QIP_DATA_HOST_DIR=/docker_data # 生产务必修改 AUTH_TOKEN_SECRET=change-me-to-a-long-random-string DEFAULT_SUPERADMIN_USERNAME=admin -DEFAULT_SUPERADMIN_PASSWORD=change-me-strong-password +DEFAULT_SUPERADMIN_PASSWORD=Sto1234@ DEFAULT_SUPERADMIN_DISPLAY_NAME=系统超管 # MCP 鉴权(建议 true) diff --git a/Jenkinsfile b/Jenkinsfile index bd1a656..051d85b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,13 +3,10 @@ pipeline { environment { DEPLOY_DIR = '/opt/quality-inspection-platform' - DATA_DIR = '/docker_data' + SSH_SERVER = '10.20.30.42' } parameters { - string(name: 'DEPLOY_HOST', defaultValue: '', description: '部署目标服务器 SSH 地址,如 root@10.0.0.5') - string(name: 'SSH_PORT', defaultValue: '22', description: 'SSH 端口') - string(name: 'SSH_CREDENTIALS', defaultValue: 'deploy-ssh-key', description: 'Jenkins 凭据 ID(SSH 私钥或用户名密码)') choice(name: 'ACTION', choices: ['deploy', 'restart', 'stop', 'logs'], description: '执行动作') } @@ -21,9 +18,6 @@ pipeline { stages { - // ────────────────────────────────────── - // 1. 拉取代码 - // ────────────────────────────────────── stage('Checkout') { steps { checkout scm @@ -35,146 +29,149 @@ pipeline { } } - // ────────────────────────────────────── - // 2. 推送源码到目标服务器 - // ────────────────────────────────────── - stage('Sync Source') { + stage('Deploy') { when { expression { params.ACTION == 'deploy' } } steps { - sshagent(credentials: [params.SSH_CREDENTIALS]) { - sh """ - ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} \ - 'mkdir -p ${DEPLOY_DIR}' + sshPublisher( + publishers: [ + sshPublisherDesc( + configName: "${SSH_SERVER}", + verbose: true, + transfers: [ + sshTransfer( + sourceFiles: '**/*', + excludes: '.git/**, .venv/**, __pycache__/**, node_modules/**, *.pyc, .env', + remoteDirectory: "${DEPLOY_DIR}", + removePrefix: '', + cleanRemote: true, + execTimeout: 600000, + execCommand: """ + set -eu + cd ${DEPLOY_DIR} - rsync -avz --delete \ - --exclude '.git' \ - --exclude '.venv' \ - --exclude '__pycache__' \ - --exclude 'node_modules' \ - --exclude '*.pyc' \ - --exclude '.env' \ - -e "ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no" \ - ./ ${params.DEPLOY_HOST}:${DEPLOY_DIR}/ - """ - } - echo '源码同步完成' + # 首次部署自动创建 .env + if [ ! -f .env ]; then + cp .env.example .env + echo "首次部署:已从 .env.example 创建 .env,请稍后修改密码和密钥" + fi + + # 确保数据目录存在 + DATA_HOST_DIR=\$(grep "^QIP_DATA_HOST_DIR=" .env | cut -d= -f2 || echo "/docker_data") + [ -z "\$DATA_HOST_DIR" ] && DATA_HOST_DIR="/docker_data" + mkdir -p "\$DATA_HOST_DIR" + + # 备份数据库 + if [ -f "\$DATA_HOST_DIR/ai_test.db" ]; then + cp "\$DATA_HOST_DIR/ai_test.db" \\ + "\$DATA_HOST_DIR/ai_test.db.bak.\$(date +%Y%m%d%H%M%S)" + echo "数据库已备份" + fi + + # 构建镜像 + DOCKER_BUILDKIT=1 docker compose build --pull + + # 重启容器 + docker compose down --remove-orphans + docker compose up -d + + echo "等待容器启动..." + sleep 20 + + # 健康检查 + PORT=\$(grep "^QIP_HOST_PORT=" .env | cut -d= -f2 || echo "8000") + [ -z "\$PORT" ] && PORT="8000" + STATUS=\$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:\$PORT/health || true) + if [ "\$STATUS" = "200" ]; then + echo "部署成功,健康检查通过 (HTTP 200)" + else + echo "健康检查未通过 (HTTP \$STATUS),查看日志:" + docker compose logs --tail 30 + exit 1 + fi + """ + ) + ] + ) + ] + ) } } - // ────────────────────────────────────── - // 3. 远程服务器 build + 启动 - // ────────────────────────────────────── - stage('Remote Build & Start') { - when { expression { params.ACTION == 'deploy' } } - steps { - sshagent(credentials: [params.SSH_CREDENTIALS]) { - sh """ - ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} ' - set -eu - cd ${DEPLOY_DIR} - - # 确保 .env 存在(首次部署从 example 复制) - if [ ! -f .env ]; then - cp .env.example .env - echo "首次部署:已从 .env.example 创建 .env,请稍后修改密码和密钥" - fi - - # 确保数据目录存在 - DATA_HOST_DIR=\$(grep "^QIP_DATA_HOST_DIR=" .env | cut -d= -f2 || echo "/docker_data") - [ -z "\$DATA_HOST_DIR" ] && DATA_HOST_DIR="/docker_data" - mkdir -p "\$DATA_HOST_DIR" - - # 备份数据库 - if [ -f "\$DATA_HOST_DIR/ai_test.db" ]; then - cp "\$DATA_HOST_DIR/ai_test.db" \ - "\$DATA_HOST_DIR/ai_test.db.bak.\$(date +%Y%m%d%H%M%S)" - echo "数据库已备份" - fi - - # 构建镜像(使用 BuildKit 加速) - DOCKER_BUILDKIT=1 docker compose build --pull - - # 重启容器 - docker compose down --remove-orphans - docker compose up -d - - echo "等待容器启动..." - sleep 20 - - # 健康检查 - PORT=\$(grep "^QIP_HOST_PORT=" .env | cut -d= -f2 || echo "8000") - [ -z "\$PORT" ] && PORT="8000" - STATUS=\$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:\$PORT/health || true) - if [ "\$STATUS" = "200" ]; then - echo "✓ 部署成功,健康检查通过 (HTTP 200)" - else - echo "✗ 健康检查未通过 (HTTP \$STATUS),查看日志:" - docker compose logs --tail 30 - exit 1 - fi - ' - """ - } - } - } - - // ────────────────────────────────────── - // 4. 仅重启(不重新 build) - // ────────────────────────────────────── - stage('Restart Only') { + stage('Restart') { when { expression { params.ACTION == 'restart' } } steps { - sshagent(credentials: [params.SSH_CREDENTIALS]) { - sh """ - ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} ' - cd ${DEPLOY_DIR} - docker compose restart - sleep 10 - curl -sf http://127.0.0.1:\$(grep "^QIP_HOST_PORT=" .env | cut -d= -f2 || echo 8000)/health \ - && echo "重启成功" || echo "重启后健康检查失败" - ' - """ - } + sshPublisher( + publishers: [ + sshPublisherDesc( + configName: "${SSH_SERVER}", + verbose: true, + transfers: [ + sshTransfer( + execTimeout: 120000, + execCommand: """ + cd ${DEPLOY_DIR} + docker compose restart + sleep 10 + curl -sf http://127.0.0.1:\$(grep "^QIP_HOST_PORT=" .env | cut -d= -f2 || echo 8000)/health \\ + && echo "重启成功" || echo "重启后健康检查失败" + """ + ) + ] + ) + ] + ) } } - // ────────────────────────────────────── - // 5. 停止 - // ────────────────────────────────────── stage('Stop') { when { expression { params.ACTION == 'stop' } } steps { - sshagent(credentials: [params.SSH_CREDENTIALS]) { - sh """ - ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} ' - cd ${DEPLOY_DIR} && docker compose down - echo "容器已停止" - ' - """ - } + sshPublisher( + publishers: [ + sshPublisherDesc( + configName: "${SSH_SERVER}", + verbose: true, + transfers: [ + sshTransfer( + execTimeout: 60000, + execCommand: """ + cd ${DEPLOY_DIR} && docker compose down + echo "容器已停止" + """ + ) + ] + ) + ] + ) } } - // ────────────────────────────────────── - // 6. 查看日志 - // ────────────────────────────────────── - stage('View Logs') { + stage('Logs') { when { expression { params.ACTION == 'logs' } } steps { - sshagent(credentials: [params.SSH_CREDENTIALS]) { - sh """ - ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} ' - cd ${DEPLOY_DIR} && docker compose logs --tail 100 - ' - """ - } + sshPublisher( + publishers: [ + sshPublisherDesc( + configName: "${SSH_SERVER}", + verbose: true, + transfers: [ + sshTransfer( + execTimeout: 30000, + execCommand: """ + cd ${DEPLOY_DIR} && docker compose logs --tail 100 + """ + ) + ] + ) + ] + ) } } } post { - success { echo "✓ 流水线执行成功: ${params.ACTION}" } - failure { echo "✗ 流水线执行失败,请检查日志" } + success { echo "流水线执行成功: ${params.ACTION}" } + failure { echo "流水线执行失败,请检查日志" } always { cleanWs() } } }