#!/bin/bash # Webhook CI 安装脚本(轻量级,适合个人小团队) # 支持 CentOS/RHEL 系统 set -e # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # 配置变量 GERRIT_HOST="${GERRIT_HOST:-101.43.95.130}" GERRIT_PORT="${GERRIT_PORT:-8080}" WEBHOOK_PORT="${WEBHOOK_PORT:-9000}" # 使用用户目录,避免需要 sudo CI_SCRIPTS_DIR="${CI_SCRIPTS_DIR:-${HOME}/ci-scripts}" WEBHOOK_USER="${USER}" echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}Webhook CI 自动化安装脚本${NC}" echo -e "${GREEN}(轻量级,适合个人小团队)${NC}" echo -e "${GREEN}========================================${NC}" # 检查 Python3 check_python() { echo -e "${GREEN}检查 Python3 环境...${NC}" if ! command -v python3 &> /dev/null; then echo -e "${RED}未检测到 Python3,请先安装 Python3${NC}" echo -e "${YELLOW}执行: sudo yum install -y python3${NC}" exit 1 fi echo -e "${GREEN}Python3 版本: $(python3 --version)${NC}" } # 创建工作目录 create_directories() { echo -e "${GREEN}创建工作目录...${NC}" mkdir -p ${CI_SCRIPTS_DIR} cd ${CI_SCRIPTS_DIR} echo -e "${GREEN}目录创建完成: ${CI_SCRIPTS_DIR}${NC}" } # 创建 Webhook 接收器 create_webhook_receiver() { echo -e "${GREEN}创建 Webhook 接收器...${NC}" cat > ${CI_SCRIPTS_DIR}/webhook_receiver.py << EOF #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Gerrit Webhook 接收器 接收 Gerrit 事件并触发构建脚本 """ from http.server import HTTPServer, BaseHTTPRequestHandler import json import subprocess import os import logging from datetime import datetime # 获取脚本所在目录 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(os.path.join(SCRIPT_DIR, 'webhook.log')), logging.StreamHandler() ] ) class WebhookHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): """重写日志方法,使用 logging""" logging.info("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), format % args)) def do_GET(self): """处理 GET 请求(健康检查)""" self.send_response(200) self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() self.wfile.write(b'

Webhook CI Receiver

Status: OK

') def do_POST(self): """处理 POST 请求(Gerrit Webhook)""" try: content_length = int(self.headers.get('Content-Length', 0)) post_data = self.rfile.read(content_length) if not post_data: self.send_response(400) self.end_headers() return # 解析 JSON try: event = json.loads(post_data.decode('utf-8')) except json.JSONDecodeError as e: logging.error(f"JSON 解析错误: {e}") self.send_response(400) self.end_headers() return # 记录事件 event_type = event.get('type', 'unknown') logging.info(f"收到事件: {event_type}") # 处理不同类型的事件 if event_type == 'patchset-created': self.handle_patchset_created(event) elif event_type == 'change-merged': self.handle_change_merged(event) elif event_type == 'comment-added': self.handle_comment_added(event) else: logging.info(f"未处理的事件类型: {event_type}") self.send_response(200) self.send_header('Content-type', 'application/json; charset=utf-8') self.end_headers() self.wfile.write(json.dumps({'status': 'ok'}).encode('utf-8')) except Exception as e: logging.error(f"处理请求时出错: {e}", exc_info=True) self.send_response(500) self.end_headers() self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8')) def handle_patchset_created(self, event): """处理 patchset-created 事件""" change = event.get('change', {}) project = change.get('project', '') branch = change.get('branch', '') change_id = change.get('id', '') patchset = event.get('patchSet', {}) patchset_number = patchset.get('number', '') logging.info(f"Patchset 创建: 项目={project}, 分支={branch}, Change={change_id}, Patchset={patchset_number}") # 触发构建脚本(异步执行) script_path = os.path.join(SCRIPT_DIR, 'build.sh') if os.path.exists(script_path): env = os.environ.copy() env.update({ 'GERRIT_PROJECT': project, 'GERRIT_BRANCH': branch, 'GERRIT_CHANGE_ID': change_id, 'GERRIT_PATCHSET_NUMBER': str(patchset_number), 'GERRIT_EVENT': 'patchset-created' }) subprocess.Popen(['bash', script_path], env=env) logging.info(f"已触发构建脚本: {script_path}") else: logging.warning(f"构建脚本不存在: {script_path}") def handle_change_merged(self, event): """处理 change-merged 事件(代码合并后)""" change = event.get('change', {}) project = change.get('project', '') branch = change.get('branch', '') logging.info(f"Change 合并: 项目={project}, 分支={branch}") # 触发部署脚本(异步执行) script_path = os.path.join(SCRIPT_DIR, 'deploy.sh') if os.path.exists(script_path): env = os.environ.copy() env.update({ 'GERRIT_PROJECT': project, 'GERRIT_BRANCH': branch, 'GERRIT_EVENT': 'change-merged' }) subprocess.Popen(['bash', script_path], env=env) logging.info(f"已触发部署脚本: {script_path}") else: logging.info(f"部署脚本不存在: {script_path}(跳过)") def handle_comment_added(self, event): """处理 comment-added 事件(代码审查评论)""" change = event.get('change', {}) comment = event.get('comment', '') # 如果评论包含特定关键词,触发构建 if 'retest' in comment.lower() or 'test' in comment.lower(): logging.info("检测到测试关键词,触发构建") self.handle_patchset_created(event) if __name__ == '__main__': port = int(os.environ.get('WEBHOOK_PORT', 9000)) server = HTTPServer(('0.0.0.0', port), WebhookHandler) logging.info(f"Webhook 接收器启动在端口 {port}") try: server.serve_forever() except KeyboardInterrupt: logging.info("收到中断信号,正在关闭...") server.shutdown() EOF chmod +x ${CI_SCRIPTS_DIR}/webhook_receiver.py echo -e "${GREEN}Webhook 接收器创建完成${NC}" } # 创建构建脚本模板 create_build_script() { echo -e "${GREEN}创建构建脚本模板...${NC}" cat > ${CI_SCRIPTS_DIR}/build.sh << 'EOF' #!/bin/bash # 构建脚本模板 # 根据 Gerrit 事件自动触发 set -e # 获取环境变量 PROJECT="${GERRIT_PROJECT}" BRANCH="${GERRIT_BRANCH}" CHANGE_ID="${GERRIT_CHANGE_ID}" PATCHSET="${GERRIT_PATCHSET_NUMBER}" EVENT="${GERRIT_EVENT}" # 获取脚本所在目录 SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)" # 日志文件 LOG_FILE="\${SCRIPT_DIR}/build.log" BUILD_DIR="/tmp/ci-builds/\${PROJECT}" echo "=========================================" | tee -a ${LOG_FILE} echo "构建开始: $(date)" | tee -a ${LOG_FILE} echo "项目: ${PROJECT}" | tee -a ${LOG_FILE} echo "分支: ${BRANCH}" | tee -a ${LOG_FILE} echo "Change ID: ${CHANGE_ID}" | tee -a ${LOG_FILE} echo "Patchset: ${PATCHSET}" | tee -a ${LOG_FILE} echo "=========================================" | tee -a ${LOG_FILE} # 创建构建目录 mkdir -p ${BUILD_DIR} cd ${BUILD_DIR} # 克隆或更新代码 if [ -d "${PROJECT}" ]; then echo "更新代码..." | tee -a ${LOG_FILE} cd ${PROJECT} git fetch origin git checkout ${BRANCH} git pull origin ${BRANCH} else echo "克隆代码..." | tee -a ${LOG_FILE} git clone ssh://admin@101.43.95.130:29418/${PROJECT} cd ${PROJECT} git checkout ${BRANCH} fi # 根据项目类型执行构建 if [ -f "pom.xml" ]; then echo "检测到 Maven 项目,执行 Maven 构建..." | tee -a ${LOG_FILE} mvn clean package -DskipTests || { echo "Maven 构建失败" | tee -a ${LOG_FILE} exit 1 } elif [ -f "package.json" ]; then echo "检测到 Node.js 项目,执行 npm 构建..." | tee -a ${LOG_FILE} npm install && npm run build || { echo "npm 构建失败" | tee -a ${LOG_FILE} exit 1 } elif [ -f "Makefile" ]; then echo "检测到 Makefile,执行 make..." | tee -a ${LOG_FILE} make || { echo "Make 构建失败" | tee -a ${LOG_FILE} exit 1 } else echo "未检测到构建配置文件,跳过构建" | tee -a ${LOG_FILE} fi echo "构建完成: $(date)" | tee -a ${LOG_FILE} echo "=========================================" | tee -a ${LOG_FILE} EOF chmod +x ${CI_SCRIPTS_DIR}/build.sh echo -e "${GREEN}构建脚本模板创建完成${NC}" } # 创建部署脚本模板 create_deploy_script() { echo -e "${GREEN}创建部署脚本模板...${NC}" cat > ${CI_SCRIPTS_DIR}/deploy.sh << 'EOF' #!/bin/bash # 部署脚本模板 # 在代码合并后自动触发 set -e PROJECT="${GERRIT_PROJECT}" BRANCH="${GERRIT_BRANCH}" # 获取脚本所在目录 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_FILE="${SCRIPT_DIR}/deploy.log" echo "=========================================" | tee -a ${LOG_FILE} echo "部署开始: $(date)" | tee -a ${LOG_FILE} echo "项目: ${PROJECT}" | tee -a ${LOG_FILE} echo "分支: ${BRANCH}" | tee -a ${LOG_FILE} echo "=========================================" | tee -a ${LOG_FILE} # 在这里添加您的部署逻辑 # 例如: # - 复制构建产物到部署目录 # - 重启服务 # - 发送通知等 echo "部署完成: $(date)" | tee -a ${LOG_FILE} echo "=========================================" | tee -a ${LOG_FILE} EOF chmod +x ${CI_SCRIPTS_DIR}/deploy.sh echo -e "${GREEN}部署脚本模板创建完成${NC}" } # 创建启动脚本 create_start_script() { echo -e "${GREEN}创建启动脚本...${NC}" cat > ${CI_SCRIPTS_DIR}/start_webhook.sh << EOF #!/bin/bash # Webhook CI 启动脚本 cd ${CI_SCRIPTS_DIR} export WEBHOOK_PORT=${WEBHOOK_PORT} # 检查是否已在运行 if pgrep -f "webhook_receiver.py" > /dev/null; then echo "Webhook CI 已在运行中" exit 1 fi # 启动服务(后台运行) nohup python3 ${CI_SCRIPTS_DIR}/webhook_receiver.py > ${CI_SCRIPTS_DIR}/webhook_stdout.log 2>&1 & echo "Webhook CI 已启动,PID: \$!" echo "日志文件: ${CI_SCRIPTS_DIR}/webhook.log" echo "标准输出: ${CI_SCRIPTS_DIR}/webhook_stdout.log" EOF chmod +x ${CI_SCRIPTS_DIR}/start_webhook.sh # 创建停止脚本 cat > ${CI_SCRIPTS_DIR}/stop_webhook.sh << EOF #!/bin/bash # Webhook CI 停止脚本 PID=\$(pgrep -f "webhook_receiver.py") if [ -n "\$PID" ]; then kill \$PID echo "Webhook CI 已停止 (PID: \$PID)" else echo "Webhook CI 未运行" fi EOF chmod +x ${CI_SCRIPTS_DIR}/stop_webhook.sh # 创建状态检查脚本 cat > ${CI_SCRIPTS_DIR}/status_webhook.sh << EOF #!/bin/bash # Webhook CI 状态检查脚本 PID=\$(pgrep -f "webhook_receiver.py") if [ -n "\$PID" ]; then echo "Webhook CI 正在运行 (PID: \$PID)" echo "端口: ${WEBHOOK_PORT}" netstat -tlnp 2>/dev/null | grep ${WEBHOOK_PORT} || ss -tlnp 2>/dev/null | grep ${WEBHOOK_PORT} || echo "端口监听检查失败" else echo "Webhook CI 未运行" fi EOF chmod +x ${CI_SCRIPTS_DIR}/status_webhook.sh echo -e "${GREEN}启动/停止脚本创建完成${NC}" } # 启动服务 start_service() { echo -e "${GREEN}启动 Webhook CI 服务...${NC}" # 检查是否已在运行 if pgrep -f "webhook_receiver.py" > /dev/null; then echo -e "${YELLOW}Webhook CI 已在运行中,跳过启动${NC}" return fi # 启动服务 cd ${CI_SCRIPTS_DIR} export WEBHOOK_PORT=${WEBHOOK_PORT} nohup python3 ${CI_SCRIPTS_DIR}/webhook_receiver.py > ${CI_SCRIPTS_DIR}/webhook_stdout.log 2>&1 & # 等待服务启动 sleep 2 if pgrep -f "webhook_receiver.py" > /dev/null; then PID=$(pgrep -f "webhook_receiver.py") echo -e "${GREEN}Webhook CI 服务启动成功!(PID: ${PID})${NC}" else echo -e "${RED}服务启动失败,请检查日志${NC}" echo -e "${YELLOW}查看日志: tail -f ${CI_SCRIPTS_DIR}/webhook.log${NC}" exit 1 fi } # 检查防火墙 check_firewall() { echo -e "${YELLOW}检查防火墙配置...${NC}" if command -v firewall-cmd &> /dev/null; then if sudo firewall-cmd --list-ports | grep -q "${WEBHOOK_PORT}"; then echo -e "${GREEN}防火墙端口 ${WEBHOOK_PORT} 已开放${NC}" else echo -e "${YELLOW}需要开放防火墙端口 ${WEBHOOK_PORT}${NC}" echo -e "${YELLOW}执行: sudo firewall-cmd --permanent --add-port=${WEBHOOK_PORT}/tcp && sudo firewall-cmd --reload${NC}" fi else echo -e "${YELLOW}未检测到 firewall-cmd,请手动检查防火墙${NC}" fi } # 输出访问信息 print_info() { echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}Webhook CI 安装完成!${NC}" echo -e "${GREEN}========================================${NC}" echo -e "${YELLOW}Webhook URL:${NC}" echo -e " http://${GERRIT_HOST}:${WEBHOOK_PORT}" echo -e "" echo -e "${YELLOW}常用命令:${NC}" echo -e " 启动: ${CI_SCRIPTS_DIR}/start_webhook.sh" echo -e " 停止: ${CI_SCRIPTS_DIR}/stop_webhook.sh" echo -e " 状态: ${CI_SCRIPTS_DIR}/status_webhook.sh" echo -e " 日志: tail -f ${CI_SCRIPTS_DIR}/webhook.log" echo -e " 标准输出: tail -f ${CI_SCRIPTS_DIR}/webhook_stdout.log" echo -e " 构建日志: tail -f ${CI_SCRIPTS_DIR}/build.log" echo -e "" echo -e "${YELLOW}脚本位置:${NC}" echo -e " Webhook 接收器: ${CI_SCRIPTS_DIR}/webhook_receiver.py" echo -e " 构建脚本: ${CI_SCRIPTS_DIR}/build.sh" echo -e " 部署脚本: ${CI_SCRIPTS_DIR}/deploy.sh" echo -e "" echo -e "${YELLOW}下一步 - 配置 Gerrit Webhook:${NC}" echo -e " 1. 访问 Gerrit: http://${GERRIT_HOST}:${GERRIT_PORT}" echo -e " 2. 进入项目设置 → Webhooks" echo -e " 3. 添加 Webhook:" echo -e " URL: http://${GERRIT_HOST}:${WEBHOOK_PORT}" echo -e " 事件: patchset-created, change-merged" echo -e " 4. 根据项目类型修改构建脚本: ${CI_SCRIPTS_DIR}/build.sh" echo -e "" echo -e "${YELLOW}测试 Webhook:${NC}" echo -e " curl http://${GERRIT_HOST}:${WEBHOOK_PORT}" echo -e "${GREEN}========================================${NC}" } # 主函数 main() { check_python create_directories create_webhook_receiver create_build_script create_deploy_script create_start_script start_service check_firewall print_info } # 执行主函数 main