#!/usr/bin/env python3 """ GitHub Webhook处理器 监听GitHub推送事件,自动触发部署 """ import os import subprocess import json import hmac import hashlib import logging from flask import Flask, request, jsonify import threading import time app = Flask(__name__) # 配置 WEBHOOK_SECRET = "kaopeilian-webhook-secret-2025" # GitHub中配置的密钥 PROJECT_DIR = "/root/aiedu" UPDATE_SCRIPT = "/root/aiedu/scripts/auto_update.sh" # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('/var/log/kaopeilian_webhook.log'), logging.StreamHandler() ] ) def verify_signature(payload_body, signature_header): """验证GitHub Webhook签名""" if not signature_header: return False hash_object = hmac.new( WEBHOOK_SECRET.encode('utf-8'), payload_body, hashlib.sha256 ) expected_signature = "sha256=" + hash_object.hexdigest() return hmac.compare_digest(expected_signature, signature_header) def run_update_async(): """异步执行更新脚本""" try: # 等待5秒再执行,避免GitHub推送过程中的竞争条件 time.sleep(5) result = subprocess.run( [UPDATE_SCRIPT], cwd=PROJECT_DIR, capture_output=True, text=True, timeout=600 # 10分钟超时 ) if result.returncode == 0: logging.info(f"Update completed successfully: {result.stdout}") else: logging.error(f"Update failed: {result.stderr}") except subprocess.TimeoutExpired: logging.error("Update script timed out") except Exception as e: logging.error(f"Error running update script: {e}") @app.route('/webhook', methods=['POST']) def github_webhook(): """处理GitHub Webhook请求""" # 验证签名 signature = request.headers.get('X-Hub-Signature-256') if not verify_signature(request.data, signature): logging.warning("Invalid webhook signature") return jsonify({"error": "Invalid signature"}), 403 # 解析请求 try: payload = request.get_json() except Exception as e: logging.error(f"Failed to parse JSON: {e}") return jsonify({"error": "Invalid JSON"}), 400 # 检查事件类型 event_type = request.headers.get('X-GitHub-Event') if event_type != 'push': logging.info(f"Ignoring event type: {event_type}") return jsonify({"message": "Event ignored"}), 200 # 检查分支 ref = payload.get('ref', '') if ref != 'refs/heads/production': logging.info(f"Ignoring push to branch: {ref}") return jsonify({"message": "Branch ignored"}), 200 # 获取提交信息 commit_info = { 'id': payload.get('after', 'unknown'), 'message': payload.get('head_commit', {}).get('message', 'No message'), 'author': payload.get('head_commit', {}).get('author', {}).get('name', 'Unknown'), 'timestamp': payload.get('head_commit', {}).get('timestamp', 'Unknown') } logging.info(f"Received push event: {commit_info}") # 异步触发更新 update_thread = threading.Thread(target=run_update_async) update_thread.daemon = True update_thread.start() return jsonify({ "message": "Update triggered successfully", "commit": commit_info }), 200 @app.route('/health', methods=['GET']) def health_check(): """健康检查端点""" return jsonify({ "status": "healthy", "service": "kaopeilian-webhook", "timestamp": time.time() }), 200 @app.route('/status', methods=['GET']) def status(): """状态检查端点""" try: # 检查项目目录 project_exists = os.path.exists(PROJECT_DIR) # 检查更新脚本 script_exists = os.path.exists(UPDATE_SCRIPT) script_executable = os.access(UPDATE_SCRIPT, os.X_OK) if script_exists else False # 检查Docker服务 docker_result = subprocess.run(['docker', 'compose', 'ps'], cwd=PROJECT_DIR, capture_output=True) docker_running = docker_result.returncode == 0 return jsonify({ "status": "ok", "checks": { "project_directory": project_exists, "update_script_exists": script_exists, "update_script_executable": script_executable, "docker_compose_running": docker_running } }), 200 except Exception as e: return jsonify({ "status": "error", "error": str(e) }), 500 if __name__ == '__main__': logging.info("Starting GitHub Webhook handler...") app.run(host='0.0.0.0', port=9000, debug=False)