Files
pit-router/webhook-server.py

157 lines
4.7 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
智队中枢 Webhook 服务
接收 Gitea Webhook 请求触发自动更新
"""
import os
import hmac
import hashlib
import subprocess
import logging
from flask import Flask, request, jsonify
from datetime import datetime
app = Flask(__name__)
# 配置
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'pit-router-webhook-secret-2026')
UPDATE_SCRIPT = '/www/wwwroot/pit-router/auto-update.sh'
LOG_FILE = '/var/log/pit-router-webhook.log'
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def verify_signature(payload: bytes, signature: str) -> bool:
"""验证 Gitea Webhook 签名"""
if not signature:
return False
expected = 'sha256=' + hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
def log_request(event: str, data: dict):
"""记录请求日志"""
ref = data.get('ref', '')
commits = data.get('commits', [])
pusher = data.get('pusher', {}).get('login', 'unknown')
logger.info(f"Event: {event} | Ref: {ref} | Pusher: {pusher} | Commits: {len(commits)}")
for commit in commits[:3]: # 只记录前3个 commit
msg = commit.get('message', '').split('\n')[0][:50]
logger.info(f" - {commit.get('id', '')[:7]}: {msg}")
@app.route('/health', methods=['GET'])
def health():
"""健康检查端点"""
return jsonify({'status': 'ok', 'service': 'pit-router-webhook'})
@app.route('/webhook/pit-router', methods=['POST'])
def handle_webhook():
"""处理 Gitea Webhook"""
try:
# 1. 验证签名
signature = request.headers.get('X-Gitea-Signature', '')
if not verify_signature(request.data, signature):
logger.warning("Invalid signature")
return jsonify({'error': 'Invalid signature'}), 401
# 2. 解析事件
event = request.headers.get('X-Gitea-Event', '')
data = request.json or {}
# 记录请求
log_request(event, data)
# 3. 只处理 push 事件
if event != 'push':
logger.info(f"Event '{event}' ignored")
return jsonify({'message': f'Event {event} ignored'}), 200
# 4. 只处理 main 分支
ref = data.get('ref', '')
if ref != 'refs/heads/main':
logger.info(f"Branch '{ref}' ignored")
return jsonify({'message': f'Branch {ref} ignored'}), 200
# 5. 执行更新脚本
logger.info("🚀 Triggering auto-update...")
result = subprocess.run(
[UPDATE_SCRIPT],
capture_output=True,
text=True,
timeout=600 # 10 分钟超时
)
# 记录输出
if result.stdout:
logger.info(f"Update output:\n{result.stdout[-2000:]}") # 只记录最后 2000 字符
if result.stderr:
logger.error(f"Update error:\n{result.stderr[-1000:]}")
if result.returncode == 0:
logger.info("✅ Update completed successfully")
return jsonify({
'status': 'success',
'message': 'Update completed',
'timestamp': datetime.utcnow().isoformat()
})
else:
logger.error(f"❌ Update failed with code {result.returncode}")
return jsonify({
'status': 'error',
'message': 'Update failed',
'details': result.stderr[-500:] if result.stderr else 'Unknown error'
}), 500
except subprocess.TimeoutExpired:
logger.error("Update timeout")
return jsonify({'error': 'Update timeout'}), 500
except Exception as e:
logger.exception(f"Unexpected error: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/webhook/test', methods=['GET'])
def test_webhook():
"""测试端点(手动触发更新)"""
try:
logger.info("🧪 Manual test trigger")
result = subprocess.run(
[UPDATE_SCRIPT],
capture_output=True,
text=True,
timeout=600
)
return jsonify({
'status': 'success' if result.returncode == 0 else 'error',
'output': result.stdout[-1000:],
'error': result.stderr[-500:] if result.stderr else None
})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
logger.info("🚀 Starting PIT Router Webhook Server on port 5001")
app.run(host='0.0.0.0', port=5001, debug=False)