webhook-deploy.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. #!/usr/bin/env python3
  2. """
  3. Webhook 部署脚本
  4. 接收 Gogs/GitHub webhook 请求,自动执行部署
  5. 用法:
  6. 1. 访问 https://book.rrbrr.com/webhook-deploy.py?secret=YOUR_SECRET
  7. 2. 或使用 curl: curl -X POST https://book.rrbrr.com/webhook-deploy.py -d "secret=YOUR_SECRET"
  8. """
  9. import os
  10. import sys
  11. import hashlib
  12. import hmac
  13. import subprocess
  14. from http.server import HTTPServer, BaseHTTPRequestHandler
  15. import json
  16. # 配置
  17. WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'your-secret-key-change-me')
  18. DEPLOY_SCRIPT = '/data/ai/deploy.sh'
  19. LOG_FILE = '/tmp/webhook-deploy.log'
  20. def log(msg):
  21. """写入日志"""
  22. timestamp = subprocess.check_output(['date', '+%Y-%m-%d %H:%M:%S']).decode().strip()
  23. with open(LOG_FILE, 'a') as f:
  24. f.write(f"[{timestamp}] {msg}\n")
  25. print(f"[{timestamp}] {msg}")
  26. def verify_signature(secret, payload, signature):
  27. """验证请求签名"""
  28. if not signature:
  29. return False
  30. mac = hmac.new(secret.encode(), payload, hashlib.sha256)
  31. return mac.hexdigest() == signature.replace('sha256=', '')
  32. def execute_deploy():
  33. """执行部署脚本"""
  34. try:
  35. log("开始执行部署...")
  36. # 切换到项目目录
  37. os.chdir('/data/ai/audio_codebuddy')
  38. # Git pull
  39. log("执行 git pull...")
  40. result = subprocess.run(['git', 'pull', 'origin', 'master'],
  41. capture_output=True, text=True, timeout=60)
  42. log(f"Git pull 结果: {result.returncode}")
  43. if result.stdout:
  44. log(f"stdout: {result.stdout}")
  45. if result.stderr:
  46. log(f"stderr: {result.stderr}")
  47. # 执行部署脚本
  48. log("执行部署脚本...")
  49. result = subprocess.run(['bash', DEPLOY_SCRIPT],
  50. capture_output=True, text=True, timeout=600)
  51. log(f"部署脚本返回: {result.returncode}")
  52. if result.stdout:
  53. log(f"stdout: {result.stdout[-2000:]}") # 只保留最后2000字符
  54. if result.stderr:
  55. log(f"stderr: {result.stderr[-2000:]}")
  56. if result.returncode == 0:
  57. log("部署成功!")
  58. return True
  59. else:
  60. log(f"部署失败! 返回码: {result.returncode}")
  61. return False
  62. except subprocess.TimeoutExpired:
  63. log("部署超时!")
  64. return False
  65. except Exception as e:
  66. log(f"部署异常: {e}")
  67. return False
  68. class WebhookHandler(BaseHTTPRequestHandler):
  69. def do_GET(self):
  70. """处理 GET 请求(健康检查)"""
  71. self.send_response(200)
  72. self.send_header('Content-type', 'text/plain')
  73. self.end_headers()
  74. self.wfile.write(b'Webhook is running!')
  75. def do_POST(self):
  76. """处理 POST 请求(webhook)"""
  77. # 读取请求体
  78. content_length = int(self.headers.get('Content-Length', 0))
  79. body = self.rfile.read(content_length)
  80. # 获取签名
  81. signature = self.headers.get('X-Hub-Signature-256') or \
  82. self.headers.get('X-Gogs-Signature') or \
  83. self.headers.get('X-Gitea-Signature')
  84. # 验证签名(如果有 secret)
  85. if WEBHOOK_SECRET != 'your-secret-key-change-me':
  86. if not verify_signature(WEBHOOK_SECRET, body, signature):
  87. log("签名验证失败!")
  88. self.send_response(401)
  89. self.send_header('Content-type', 'application/json')
  90. self.end_headers()
  91. self.wfile.write(json.dumps({'error': 'Invalid signature'}).encode())
  92. return
  93. # 解析 payload
  94. try:
  95. payload = json.loads(body)
  96. log(f"收到 webhook: {payload.get('ref', 'unknown')}")
  97. except:
  98. log(f"无法解析 payload: {body[:200]}")
  99. payload = {}
  100. # 执行部署
  101. success = execute_deploy()
  102. # 返回结果
  103. self.send_response(200 if success else 500)
  104. self.send_header('Content-type', 'application/json')
  105. self.end_headers()
  106. result = {'success': success, 'message': 'Deployment completed'}
  107. self.wfile.write(json.dumps(result).encode())
  108. def log_message(self, format, *args):
  109. """禁用默认日志"""
  110. pass
  111. def main():
  112. port = int(os.environ.get('WEBHOOK_PORT', 8080))
  113. server = HTTPServer(('0.0.0.0', port), WebhookHandler)
  114. log(f"Webhook 服务启动在端口 {port}")
  115. print(f"Webhook 服务启动在端口 {port}")
  116. server.serve_forever()
  117. if __name__ == '__main__':
  118. main()