pipeline { agent any environment { DEPLOY_DIR = '/opt/quality-inspection-platform' DATA_DIR = '/docker_data' } 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: '执行动作') } options { timeout(time: 30, unit: 'MINUTES') disableConcurrentBuilds() buildDiscarder(logRotator(numToKeepStr: '20')) } stages { // ────────────────────────────────────── // 1. 拉取代码 // ────────────────────────────────────── stage('Checkout') { steps { checkout scm script { env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() env.GIT_BRANCH_NAME = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim() } echo "分支: ${env.GIT_BRANCH_NAME} 提交: ${env.GIT_COMMIT_SHORT}" } } // ────────────────────────────────────── // 2. 推送源码到目标服务器 // ────────────────────────────────────── stage('Sync Source') { 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}' 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 '源码同步完成' } } // ────────────────────────────────────── // 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') { 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 "重启后健康检查失败" ' """ } } } // ────────────────────────────────────── // 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 "容器已停止" ' """ } } } // ────────────────────────────────────── // 6. 查看日志 // ────────────────────────────────────── stage('View 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 ' """ } } } } post { success { echo "✓ 流水线执行成功: ${params.ACTION}" } failure { echo "✗ 流水线执行失败,请检查日志" } always { cleanWs() } } }