#!/usr/bin/env python3 """ Webhook 部署脚本 接收 Gogs/GitHub webhook 请求,自动执行部署 用法: 1. 访问 https://book.rrbrr.com/webhook-deploy.py?secret=YOUR_SECRET 2. 或使用 curl: curl -X POST https://book.rrbrr.com/webhook-deploy.py -d "secret=YOUR_SECRET" """ import os import sys import hashlib import hmac import subprocess from http.server import HTTPServer, BaseHTTPRequestHandler import json # 配置 WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'your-secret-key-change-me') DEPLOY_SCRIPT = '/data/ai/deploy.sh' LOG_FILE = '/tmp/webhook-deploy.log' def log(msg): """写入日志""" timestamp = subprocess.check_output(['date', '+%Y-%m-%d %H:%M:%S']).decode().strip() with open(LOG_FILE, 'a') as f: f.write(f"[{timestamp}] {msg}\n") print(f"[{timestamp}] {msg}") def verify_signature(secret, payload, signature): """验证请求签名""" if not signature: return False mac = hmac.new(secret.encode(), payload, hashlib.sha256) return mac.hexdigest() == signature.replace('sha256=', '') def execute_deploy(): """执行部署脚本""" try: log("开始执行部署...") # 切换到项目目录 os.chdir('/data/ai/audio_codebuddy') # Git pull log("执行 git pull...") result = subprocess.run(['git', 'pull', 'origin', 'master'], capture_output=True, text=True, timeout=60) log(f"Git pull 结果: {result.returncode}") if result.stdout: log(f"stdout: {result.stdout}") if result.stderr: log(f"stderr: {result.stderr}") # 执行部署脚本 log("执行部署脚本...") result = subprocess.run(['bash', DEPLOY_SCRIPT], capture_output=True, text=True, timeout=600) log(f"部署脚本返回: {result.returncode}") if result.stdout: log(f"stdout: {result.stdout[-2000:]}") # 只保留最后2000字符 if result.stderr: log(f"stderr: {result.stderr[-2000:]}") if result.returncode == 0: log("部署成功!") return True else: log(f"部署失败! 返回码: {result.returncode}") return False except subprocess.TimeoutExpired: log("部署超时!") return False except Exception as e: log(f"部署异常: {e}") return False class WebhookHandler(BaseHTTPRequestHandler): def do_GET(self): """处理 GET 请求(健康检查)""" self.send_response(200) self.send_header('Content-type', 'text/plain') self.end_headers() self.wfile.write(b'Webhook is running!') def do_POST(self): """处理 POST 请求(webhook)""" # 读取请求体 content_length = int(self.headers.get('Content-Length', 0)) body = self.rfile.read(content_length) # 获取签名 signature = self.headers.get('X-Hub-Signature-256') or \ self.headers.get('X-Gogs-Signature') or \ self.headers.get('X-Gitea-Signature') # 验证签名(如果有 secret) if WEBHOOK_SECRET != 'your-secret-key-change-me': if not verify_signature(WEBHOOK_SECRET, body, signature): log("签名验证失败!") self.send_response(401) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps({'error': 'Invalid signature'}).encode()) return # 解析 payload try: payload = json.loads(body) log(f"收到 webhook: {payload.get('ref', 'unknown')}") except: log(f"无法解析 payload: {body[:200]}") payload = {} # 执行部署 success = execute_deploy() # 返回结果 self.send_response(200 if success else 500) self.send_header('Content-type', 'application/json') self.end_headers() result = {'success': success, 'message': 'Deployment completed'} self.wfile.write(json.dumps(result).encode()) def log_message(self, format, *args): """禁用默认日志""" pass def main(): port = int(os.environ.get('WEBHOOK_PORT', 8080)) server = HTTPServer(('0.0.0.0', port), WebhookHandler) log(f"Webhook 服务启动在端口 {port}") print(f"Webhook 服务启动在端口 {port}") server.serve_forever() if __name__ == '__main__': main()